From 56f42130dd9acbe5cbfec1864f877ca9c016c79c Mon Sep 17 00:00:00 2001 From: Rupadana <34137674+rupadana@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:53:17 +0800 Subject: [PATCH 01/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d91efec..28dc90a 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ You can edit prefix & group route name as you want, default im use model singula ## How to secure it? -Basically, when u register the ApiService to the `routes/api.php` you can group it using `sanctum` middleware, Whichis this is default api authentication by Laravel. [Read mode](https://laravel.com/docs/10.x/sanctum) about laravel sanctum +Basically, when u register the ApiService to the `routes/api.php` you can group it using `sanctum` middleware, Whichis this is default api authentication by Laravel. [Read more](https://laravel.com/docs/10.x/sanctum) about laravel sanctum ## Changelog From d82ba7f4e738fceb4017277bf06839796e770978 Mon Sep 17 00:00:00 2001 From: Rupadana <34137674+rupadana@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:51:20 +0800 Subject: [PATCH 02/31] Update README.md --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28dc90a..0280afb 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,36 @@ php artisan make:filament-api-transformer Blog it will be create BlogTransformer in `App\Filament\Resources\BlogResource\Api\Transformers` + +```php +resource->toArray(); + + // or + + return [ + "modified_name" => $this->name . ' so Cool!' + ] + } +} +``` + + next step you need to edit & add it to your Resource ```php @@ -102,7 +132,7 @@ next step you need to edit & add it to your Resource ## Group Name & Prefix -You can edit prefix & group route name as you want, default im use model singular label; +You can edit prefix & group route name as you want, default this plugin use model singular label; ```php class BlogApiService extends ApiService From edd54de3f2a80eadfe815e6243251abaf9cef5eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 12:00:39 +0000 Subject: [PATCH 03/31] Bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 Bumps [aglipanci/laravel-pint-action](https://github.com/aglipanci/laravel-pint-action) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/aglipanci/laravel-pint-action/releases) - [Commits](https://github.com/aglipanci/laravel-pint-action/compare/2.3.0...2.3.1) --- updated-dependencies: - dependency-name: aglipanci/laravel-pint-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-styling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-styling.yml b/.github/workflows/fix-php-code-styling.yml index a17569a..2067a66 100644 --- a/.github/workflows/fix-php-code-styling.yml +++ b/.github/workflows/fix-php-code-styling.yml @@ -19,7 +19,7 @@ jobs: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.3.0 + uses: aglipanci/laravel-pint-action@2.3.1 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v5 From bae199c24d6d14008bf4b16085d67e0688bcff9d Mon Sep 17 00:00:00 2001 From: luttje <2738114+luttje@users.noreply.github.com> Date: Mon, 1 Jan 2024 22:34:45 +0100 Subject: [PATCH 04/31] add test suite + remove some unused content (test mixin, migration, factory) --- README.md | 12 +- composer.json | 5 +- database/factories/ModelFactory.php | 19 --- .../create_api_service_table.php.stub | 19 --- src/ApiServiceServiceProvider.php | 3 - src/Testing/TestsApiService.php | 13 -- tests/ApiServiceTest.php | 131 ++++++++++++++++++ tests/ExampleTest.php | 5 - .../Migrations/01_create_products_table.php | 28 ++++ .../Database/Seeders/ProductsSeeder.php | 61 ++++++++ tests/Fixtures/Models/Product.php | 34 +++++ .../Handlers/CreateHandler.php | 32 +++++ .../Handlers/DeleteHandler.php | 32 +++++ .../Handlers/DetailHandler.php | 29 ++++ .../Handlers/PaginationHandler.php | 27 ++++ .../Handlers/UpdateHandler.php | 34 +++++ .../ProductApiService/ProductApiService.php | 23 +++ tests/Fixtures/Resources/ProductResource.php | 51 +++++++ .../Api/ProductTransformer.php | 24 ++++ tests/TestCase.php | 44 ++++-- 20 files changed, 544 insertions(+), 82 deletions(-) delete mode 100644 database/factories/ModelFactory.php delete mode 100644 database/migrations/create_api_service_table.php.stub delete mode 100644 src/Testing/TestsApiService.php create mode 100644 tests/ApiServiceTest.php delete mode 100644 tests/ExampleTest.php create mode 100644 tests/Fixtures/Database/Migrations/01_create_products_table.php create mode 100644 tests/Fixtures/Database/Seeders/ProductsSeeder.php create mode 100644 tests/Fixtures/Models/Product.php create mode 100644 tests/Fixtures/ProductApiService/Handlers/CreateHandler.php create mode 100644 tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php create mode 100644 tests/Fixtures/ProductApiService/Handlers/DetailHandler.php create mode 100644 tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php create mode 100644 tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php create mode 100644 tests/Fixtures/ProductApiService/ProductApiService.php create mode 100644 tests/Fixtures/Resources/ProductResource.php create mode 100644 tests/Fixtures/Resources/ProductResource/Api/ProductTransformer.php diff --git a/README.md b/README.md index 0280afb..9b6a260 100644 --- a/README.md +++ b/README.md @@ -35,26 +35,26 @@ and then you will got this routes: - [POST] '/api/blogs' - Create resource - [DELETE] '/api/blogs/1' - Delete resource - On CreateHandler, you need to be create your custom request validation. -Im using `"spatie/laravel-query-builder": "^5.3"` to handle query and filtering. u can see `"spatie/laravel-query-builder": "^5.3"` [https://spatie.be/docs/laravel-query-builder/v5/introduction](documentation) - +Im using `"spatie/laravel-query-builder": "^5.3"` to handle query selecting, sorting and filtering. Check out [the spatie/laravel-query-builder documentation](https://spatie.be/docs/laravel-query-builder/v5/introduction) for more information. -You can specified `allowedFilters` and `allowedFields` in your model +You can specified `allowedFilters` and `allowedFields` in your model. For example: -Example ```php class User extends Model { + // Which fields can be selected from the database through the query string public static array $allowedFields = [ 'name' ]; + // Which fields can be used to sort the results through the query string public static array $allowedSorts = [ 'name', 'created_at' ]; + // Which fields can be used to filter the results through the query string public static array $allowedFilters = [ 'name' ]; @@ -107,7 +107,7 @@ class BlogTransformer extends JsonResource return [ "modified_name" => $this->name . ' so Cool!' - ] + ]; } } ``` diff --git a/composer.json b/composer.json index fe181c6..ecaa7da 100644 --- a/composer.json +++ b/composer.json @@ -35,8 +35,7 @@ }, "autoload": { "psr-4": { - "Rupadana\\ApiService\\": "src/", - "Rupadana\\ApiService\\Database\\Factories\\": "database/factories/" + "Rupadana\\ApiService\\": "src/" } }, "autoload-dev": { @@ -68,4 +67,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} \ No newline at end of file +} diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php deleted file mode 100644 index 724ac0e..0000000 --- a/database/factories/ModelFactory.php +++ /dev/null @@ -1,19 +0,0 @@ -id(); - - // add fields - - $table->timestamps(); - }); - } -}; diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index c7bff2f..cfe422e 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -85,9 +85,6 @@ public function packageBooted(): void ], 'api-service-stubs'); } } - - // Testing - Testable::mixin(new TestsApiService()); } protected function getAssetPackageName(): ?string diff --git a/src/Testing/TestsApiService.php b/src/Testing/TestsApiService.php deleted file mode 100644 index c618bcb..0000000 --- a/src/Testing/TestsApiService.php +++ /dev/null @@ -1,13 +0,0 @@ -getRoutes())->map(function (RoutingRoute $route) { + return implode('|', $route->methods()) . ' ' . $route->uri(); + }); + + // The route name is customized to `our-products` in the `ProductApiService` class + expect($routes)->toContain('POST api/our-products'); + expect($routes)->toContain('PUT api/our-products/{id}'); + expect($routes)->toContain('DELETE api/our-products/{id}'); + expect($routes)->toContain('GET|HEAD api/our-products'); + expect($routes)->toContain('GET|HEAD api/our-products/{id}'); +}); + +it('can return a list of products with allowed attributes', function () { + $this->seed(ProductsSeeder::class); + + $response = $this->get('/api/our-products') + ->assertStatus(200); + + $products = Product::all()->map(function ($product) { + return [ + 'name' => $product['name'], + 'description' => $product['description'], + 'price' => $product['price'], + ]; + })->toArray(); + + foreach ($products as $product) { + $response->assertJsonFragment($product); + } + + // Check that the slug (hidden) is not returned + $response->assertJsonMissing([ + 'slug' => 't-shirt', + ]); +}); + +it('can return a list of products with selected fields', function () { + $this->seed(ProductsSeeder::class); + + $response = $this->get('/api/our-products?fields[products]=name,price') + ->assertStatus(200); + + $response->assertJsonFragment([ + 'name' => 'T-Shirt', + 'price' => 500, + ]); +}); + +it('throws when selecting a field that is not allowed', function () { + $this->seed(ProductsSeeder::class); + + $this->get('/api/our-products?fields[products]=name,slug,price') + ->assertStatus(400) + ->assertJsonFragment([ + 'message' => 'Requested field(s) `products.slug` are not allowed.', + ]); +})->throws(\Spatie\QueryBuilder\Exceptions\InvalidFieldQuery::class); + +it('can return a list of products with selected sorts', function () { + $this->seed(ProductsSeeder::class); + + $response = $this->get('/api/our-products?sort=-price') + ->assertStatus(200); + + $data = Product::all() + ->sortByDesc('price') + ->values() + ->map(function ($product) { + return [ + 'name' => $product['name'], + 'price' => $product['price'], + ]; + }) + ->toArray(); + + foreach ($data as $product) { + $response->assertJsonFragment($product); + } +}); + +it('throws when sorting by a field that is not allowed', function () { + $this->seed(ProductsSeeder::class); + + $this->get('/api/our-products?sort=-slug') + ->assertStatus(400) + ->assertJsonFragment([ + 'message' => 'Requested sort(s) `products.slug` are not allowed.', + ]); +})->throws(\Spatie\QueryBuilder\Exceptions\InvalidSortQuery::class); + +it('can return a list of products with selected filters', function () { + $this->seed(ProductsSeeder::class); + + $response = $this->get('/api/our-products?filter[name]=T-Shirt&filter[price]=500') + ->assertStatus(200); + + $response->assertJsonFragment([ + 'name' => 'T-Shirt', + 'price' => 500, + ]); +}); + +it('throws when filtering by a field that is not allowed', function () { + $this->seed(ProductsSeeder::class); + + $this->get('/api/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt') + ->assertStatus(400) + ->assertJsonFragment([ + 'message' => 'Requested filter(s) `products.slug` are not allowed.', + ]); +})->throws(\Spatie\QueryBuilder\Exceptions\InvalidFilterQuery::class); + +it('can return a list of products with a custom transformer', function () { + $this->seed(ProductsSeeder::class); + + $response = $this->get('/api/our-products') + ->assertStatus(200); + + $product = Product::first(); + + $response->assertJsonFragment([ + 'hash' => md5($product['name']), + ]); +}); diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 5d36321..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -toBeTrue(); -}); diff --git a/tests/Fixtures/Database/Migrations/01_create_products_table.php b/tests/Fixtures/Database/Migrations/01_create_products_table.php new file mode 100644 index 0000000..009df5b --- /dev/null +++ b/tests/Fixtures/Database/Migrations/01_create_products_table.php @@ -0,0 +1,28 @@ +id(); + + $table->string('name'); + $table->string('slug')->unique(); + + $table->text('description')->nullable(); + + $table->decimal('price', 15, 2)->default(0); + + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('products'); + } +}; diff --git a/tests/Fixtures/Database/Seeders/ProductsSeeder.php b/tests/Fixtures/Database/Seeders/ProductsSeeder.php new file mode 100644 index 0000000..e5a8334 --- /dev/null +++ b/tests/Fixtures/Database/Seeders/ProductsSeeder.php @@ -0,0 +1,61 @@ + 'Jeans', + 'slug' => 'jeans', + 'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'price' => 1000, + ]); + + Product::create([ + 'name' => 'T-Shirt', + 'slug' => 't-shirt', + 'description' => 'Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo.', + 'price' => 500, + ]); + + Product::create([ + 'name' => 'Shoes', + 'slug' => 'shoes', + 'description' => 'Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit.', + 'price' => 2000, + ]); + + Product::create([ + 'name' => 'Hat', + 'slug' => 'hat', + 'description' => 'Maecenas sed diam eget risus varius blandit sit amet non magna.', + 'price' => 100, + ]); + + Product::create([ + 'name' => 'Socks', + 'slug' => 'socks', + 'description' => 'Cras mattis consectetur purus sit amet fermentum.', + 'price' => 50, + ]); + + Product::create([ + 'name' => 'Gloves', + 'slug' => 'gloves', + 'description' => 'Maecenas sed diam eget risus varius blandit sit amet non magna.', + 'price' => 150, + ]); + + Product::create([ + 'name' => 'Jacket', + 'slug' => 'jacket', + 'description' => 'Maecenas sed diam eget risus varius blandit sit amet non magna.', + 'price' => 200, + ]); + } +} diff --git a/tests/Fixtures/Models/Product.php b/tests/Fixtures/Models/Product.php new file mode 100644 index 0000000..289b2d5 --- /dev/null +++ b/tests/Fixtures/Models/Product.php @@ -0,0 +1,34 @@ +fill($request->all()); + + $model->save(); + + return static::sendSuccessResponse($model, "Successfully Create Resource"); + } +} diff --git a/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php b/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php new file mode 100644 index 0000000..5ae9fa2 --- /dev/null +++ b/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php @@ -0,0 +1,32 @@ +delete(); + + return static::sendSuccessResponse($model, "Successfully Delete Resource"); + } +} diff --git a/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php b/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php new file mode 100644 index 0000000..c221ff2 --- /dev/null +++ b/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php @@ -0,0 +1,29 @@ +where(static::getKeyName(), $id) + ) + ->first(); + + if (!$query) return static::sendNotFoundResponse(); + + $transformer = static::getApiTransformer(); + + return new $transformer($query); + } +} diff --git a/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php b/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php new file mode 100644 index 0000000..75ddebc --- /dev/null +++ b/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php @@ -0,0 +1,27 @@ +allowedFields($model::$allowedFields ?? []) + ->allowedSorts($model::$allowedSorts ?? []) + ->allowedFilters($model::$allowedFilters ?? []) + ->paginate(request()->query('per_page')) + ->appends(request()->query()); + + return static::getApiTransformer()::collection($query); + } +} + diff --git a/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php b/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php new file mode 100644 index 0000000..5225c7c --- /dev/null +++ b/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php @@ -0,0 +1,34 @@ +fill($request->all()); + + $model->save(); + + return static::sendSuccessResponse($model, "Successfully Update Resource"); + } +} diff --git a/tests/Fixtures/ProductApiService/ProductApiService.php b/tests/Fixtures/ProductApiService/ProductApiService.php new file mode 100644 index 0000000..65e6a98 --- /dev/null +++ b/tests/Fixtures/ProductApiService/ProductApiService.php @@ -0,0 +1,23 @@ +schema([ + TextInput::make('name') + ->maxLength(255) + ->required(), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('name') + ->searchable() + ->sortable(), + ]); + } +} diff --git a/tests/Fixtures/Resources/ProductResource/Api/ProductTransformer.php b/tests/Fixtures/Resources/ProductResource/Api/ProductTransformer.php new file mode 100644 index 0000000..1f2908a --- /dev/null +++ b/tests/Fixtures/Resources/ProductResource/Api/ProductTransformer.php @@ -0,0 +1,24 @@ +resource->toArray(); + + return [ + ...$product, + 'hash' => md5($product['name']), + ]; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index e38fa80..0ced0e0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,26 +9,24 @@ use Filament\Forms\FormsServiceProvider; use Filament\Infolists\InfolistsServiceProvider; use Filament\Notifications\NotificationsServiceProvider; -use Filament\SpatieLaravelSettingsPluginServiceProvider; -use Filament\SpatieLaravelTranslatablePluginServiceProvider; use Filament\Support\SupportServiceProvider; use Filament\Tables\TablesServiceProvider; use Filament\Widgets\WidgetsServiceProvider; -use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Contracts\Config\Repository; +use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\LivewireServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; use Rupadana\ApiService\ApiServiceServiceProvider; +use Rupadana\ApiService\Tests\Fixtures\ProductApiService\ProductApiService; use RyanChandler\BladeCaptureDirective\BladeCaptureDirectiveServiceProvider; class TestCase extends Orchestra { + use RefreshDatabase; + protected function setUp(): void { parent::setUp(); - - Factory::guessFactoryNamesUsing( - fn (string $modelName) => 'Rupadana\\ApiService\\Database\\Factories\\'.class_basename($modelName).'Factory' - ); } protected function getPackageProviders($app) @@ -43,8 +41,6 @@ protected function getPackageProviders($app) InfolistsServiceProvider::class, LivewireServiceProvider::class, NotificationsServiceProvider::class, - SpatieLaravelSettingsPluginServiceProvider::class, - SpatieLaravelTranslatablePluginServiceProvider::class, SupportServiceProvider::class, TablesServiceProvider::class, WidgetsServiceProvider::class, @@ -54,11 +50,31 @@ protected function getPackageProviders($app) public function getEnvironmentSetUp($app) { - config()->set('database.default', 'testing'); + tap($app['config'], function (Repository $config) { + $config->set('database.default', 'testing'); + $config->set('database.connections.testing', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); - /* - $migration = include __DIR__.'/../database/migrations/create_api-service_table.php.stub'; - $migration->up(); - */ + $config->set('app.env', env('APP_ENV', 'testing')); + $config->set('app.debug', env('APP_DEBUG', true)); + $config->set('app.key', 'base64:Hupx3yAySikrM2/edkZQNQHslgDWYfiBfCuSThJ5SK8='); + }); + } + + protected function defineDatabaseMigrations() + { + $this->loadLaravelMigrations(); + + // Migrations for test fixtures + $this->loadMigrationsFrom(realpath(__DIR__ . '/Fixtures/Database/Migrations')); + } + + protected function defineRoutes($router) + { + $router->group(['prefix' => 'api'], function () { + ProductApiService::routes(); + }); } } From 4b727c0d770931762767fe18fda52dce72bbb7c3 Mon Sep 17 00:00:00 2001 From: luttje <2738114+luttje@users.noreply.github.com> Date: Tue, 2 Jan 2024 13:28:30 +0100 Subject: [PATCH 05/31] composer specify phpunit version to fix workflow error --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ecaa7da..a64dbfb 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,8 @@ "orchestra/testbench": "^8.0", "pestphp/pest": "^2.0", "pestphp/pest-plugin-arch": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0" + "pestphp/pest-plugin-laravel": "^2.0", + "phpunit/phpunit": "^10.0.17" }, "autoload": { "psr-4": { From eeae77f4e846dd90d01a4b624f2f61354c3fb00c Mon Sep 17 00:00:00 2001 From: Rupadana <34137674+rupadana@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:10:55 +0800 Subject: [PATCH 06/31] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 0280afb..29b9e14 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,14 @@ You can edit prefix & group route name as you want, default this plugin use mode Basically, when u register the ApiService to the `routes/api.php` you can group it using `sanctum` middleware, Whichis this is default api authentication by Laravel. [Read more](https://laravel.com/docs/10.x/sanctum) about laravel sanctum +### Example + +```php +Route::middleware('auth:sanctum')->group(function() { + BlogApiService::routes(); +}); +``` + ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. From 593bc3d69bad4d37fd283543fa462f1c599e71dd Mon Sep 17 00:00:00 2001 From: luttje <2738114+luttje@users.noreply.github.com> Date: Thu, 4 Jan 2024 18:44:37 +0100 Subject: [PATCH 07/31] Fix broken tests because query builder service provider didn't always get discovered --- tests/TestCase.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 0ced0e0..2079966 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,6 +19,7 @@ use Rupadana\ApiService\ApiServiceServiceProvider; use Rupadana\ApiService\Tests\Fixtures\ProductApiService\ProductApiService; use RyanChandler\BladeCaptureDirective\BladeCaptureDirectiveServiceProvider; +use Spatie\QueryBuilder\QueryBuilderServiceProvider; class TestCase extends Orchestra { @@ -44,6 +45,7 @@ protected function getPackageProviders($app) SupportServiceProvider::class, TablesServiceProvider::class, WidgetsServiceProvider::class, + QueryBuilderServiceProvider::class, ApiServiceServiceProvider::class, ]; } From e2232bcae4e8d7c0c6ae6fa4e831a4ca5ffc8f6d Mon Sep 17 00:00:00 2001 From: rupadana Date: Fri, 5 Jan 2024 00:09:26 +0000 Subject: [PATCH 08/31] Fix styling --- src/ApiServiceServiceProvider.php | 2 -- tests/ApiServiceTest.php | 2 +- .../Migrations/01_create_products_table.php | 3 ++- .../Handlers/CreateHandler.php | 13 ++++++++----- .../Handlers/DeleteHandler.php | 17 +++++++++++------ .../Handlers/DetailHandler.php | 9 ++++++--- .../Handlers/PaginationHandler.php | 9 +++++---- .../Handlers/UpdateHandler.php | 17 +++++++++++------ .../ProductApiService/ProductApiService.php | 7 +++---- tests/TestCase.php | 2 +- 10 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index cfe422e..15793e1 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -6,11 +6,9 @@ use Filament\Support\Facades\FilamentAsset; use Filament\Support\Facades\FilamentIcon; use Illuminate\Filesystem\Filesystem; -use Livewire\Features\SupportTesting\Testable; use Rupadana\ApiService\Commands\MakeApiHandlerCommand; use Rupadana\ApiService\Commands\MakeApiServiceCommand; use Rupadana\ApiService\Commands\MakeApiTransformerCommand; -use Rupadana\ApiService\Testing\TestsApiService; use Spatie\LaravelPackageTools\Commands\InstallCommand; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; diff --git a/tests/ApiServiceTest.php b/tests/ApiServiceTest.php index b19d54f..716ecad 100644 --- a/tests/ApiServiceTest.php +++ b/tests/ApiServiceTest.php @@ -6,7 +6,7 @@ it('can make routes for a product resource', function () { $routes = collect(app('router')->getRoutes())->map(function (RoutingRoute $route) { - return implode('|', $route->methods()) . ' ' . $route->uri(); + return implode('|', $route->methods()).' '.$route->uri(); }); // The route name is customized to `our-products` in the `ProductApiService` class diff --git a/tests/Fixtures/Database/Migrations/01_create_products_table.php b/tests/Fixtures/Database/Migrations/01_create_products_table.php index 009df5b..88d847a 100644 --- a/tests/Fixtures/Database/Migrations/01_create_products_table.php +++ b/tests/Fixtures/Database/Migrations/01_create_products_table.php @@ -4,7 +4,8 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class () extends Migration { +return new class() extends Migration +{ public function up() { Schema::create('products', function (Blueprint $table) { diff --git a/tests/Fixtures/ProductApiService/Handlers/CreateHandler.php b/tests/Fixtures/ProductApiService/Handlers/CreateHandler.php index 93c5931..fb3a547 100644 --- a/tests/Fixtures/ProductApiService/Handlers/CreateHandler.php +++ b/tests/Fixtures/ProductApiService/Handlers/CreateHandler.php @@ -6,16 +6,19 @@ use Rupadana\ApiService\Http\Handlers; use Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource; -class CreateHandler extends Handlers { - public static string | null $uri = '/'; - public static string | null $resource = ProductResource::class; +class CreateHandler extends Handlers +{ + public static ?string $uri = '/'; + + public static ?string $resource = ProductResource::class; public static function getMethod() { return Handlers::POST; } - public static function getModel() { + public static function getModel() + { return static::$resource::getModel(); } @@ -27,6 +30,6 @@ public function handler(Request $request) $model->save(); - return static::sendSuccessResponse($model, "Successfully Create Resource"); + return static::sendSuccessResponse($model, 'Successfully Create Resource'); } } diff --git a/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php b/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php index 5ae9fa2..846a3c9 100644 --- a/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php +++ b/tests/Fixtures/ProductApiService/Handlers/DeleteHandler.php @@ -6,16 +6,19 @@ use Rupadana\ApiService\Http\Handlers; use Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource; -class DeleteHandler extends Handlers { - public static string | null $uri = '/{id}'; - public static string | null $resource = ProductResource::class; +class DeleteHandler extends Handlers +{ + public static ?string $uri = '/{id}'; + + public static ?string $resource = ProductResource::class; public static function getMethod() { return Handlers::DELETE; } - public static function getModel() { + public static function getModel() + { return static::$resource::getModel(); } @@ -23,10 +26,12 @@ public function handler(Request $request, $id) { $model = static::getModel()::find($id); - if (!$model) return static::sendNotFoundResponse(); + if (! $model) { + return static::sendNotFoundResponse(); + } $model->delete(); - return static::sendSuccessResponse($model, "Successfully Delete Resource"); + return static::sendSuccessResponse($model, 'Successfully Delete Resource'); } } diff --git a/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php b/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php index c221ff2..57c41e4 100644 --- a/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php +++ b/tests/Fixtures/ProductApiService/Handlers/DetailHandler.php @@ -8,8 +8,9 @@ class DetailHandler extends Handlers { - public static string | null $uri = '/{id}'; - public static string | null $resource = ProductResource::class; + public static ?string $uri = '/{id}'; + + public static ?string $resource = ProductResource::class; public function handler($id) { @@ -20,7 +21,9 @@ public function handler($id) ) ->first(); - if (!$query) return static::sendNotFoundResponse(); + if (! $query) { + return static::sendNotFoundResponse(); + } $transformer = static::getApiTransformer(); diff --git a/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php b/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php index 75ddebc..7be392d 100644 --- a/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php +++ b/tests/Fixtures/ProductApiService/Handlers/PaginationHandler.php @@ -6,9 +6,11 @@ use Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource; use Spatie\QueryBuilder\QueryBuilder; -class PaginationHandler extends Handlers { - public static string | null $uri = '/'; - public static string | null $resource = ProductResource::class; +class PaginationHandler extends Handlers +{ + public static ?string $uri = '/'; + + public static ?string $resource = ProductResource::class; public function handler() { @@ -24,4 +26,3 @@ public function handler() return static::getApiTransformer()::collection($query); } } - diff --git a/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php b/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php index 5225c7c..847b5c2 100644 --- a/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php +++ b/tests/Fixtures/ProductApiService/Handlers/UpdateHandler.php @@ -6,16 +6,19 @@ use Rupadana\ApiService\Http\Handlers; use Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource; -class UpdateHandler extends Handlers { - public static string | null $uri = '/{id}'; - public static string | null $resource = ProductResource::class; +class UpdateHandler extends Handlers +{ + public static ?string $uri = '/{id}'; + + public static ?string $resource = ProductResource::class; public static function getMethod() { return Handlers::PUT; } - public static function getModel() { + public static function getModel() + { return static::$resource::getModel(); } @@ -23,12 +26,14 @@ public function handler(Request $request, $id) { $model = static::getModel()::find($id); - if (!$model) return static::sendNotFoundResponse(); + if (! $model) { + return static::sendNotFoundResponse(); + } $model->fill($request->all()); $model->save(); - return static::sendSuccessResponse($model, "Successfully Update Resource"); + return static::sendSuccessResponse($model, 'Successfully Update Resource'); } } diff --git a/tests/Fixtures/ProductApiService/ProductApiService.php b/tests/Fixtures/ProductApiService/ProductApiService.php index 65e6a98..f5d5c55 100644 --- a/tests/Fixtures/ProductApiService/ProductApiService.php +++ b/tests/Fixtures/ProductApiService/ProductApiService.php @@ -2,15 +2,14 @@ namespace Rupadana\ApiService\Tests\Fixtures\ProductApiService; -use Rupadana\ApiService\ApiService; use Illuminate\Routing\Router; - +use Rupadana\ApiService\ApiService; class ProductApiService extends ApiService { - protected static string | null $resource = \Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource::class; + protected static ?string $resource = \Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource::class; - protected static string | null $groupRouteName = 'our-products'; // customize route name + protected static ?string $groupRouteName = 'our-products'; // customize route name public static function allRoutes(Router $router) { diff --git a/tests/TestCase.php b/tests/TestCase.php index 2079966..978288a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -70,7 +70,7 @@ protected function defineDatabaseMigrations() $this->loadLaravelMigrations(); // Migrations for test fixtures - $this->loadMigrationsFrom(realpath(__DIR__ . '/Fixtures/Database/Migrations')); + $this->loadMigrationsFrom(realpath(__DIR__.'/Fixtures/Database/Migrations')); } protected function defineRoutes($router) From 0fbaf3ecebefbc1b207d2ef6067433ca3a8b575b Mon Sep 17 00:00:00 2001 From: Rupadana <34137674+rupadana@users.noreply.github.com> Date: Fri, 5 Jan 2024 08:16:45 +0800 Subject: [PATCH 09/31] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cb61986..6b56025 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # A simple api service for supporting filamentphp [![Total Downloads](https://img.shields.io/packagist/dt/rupadana/filament-api-service.svg?style=flat-square)](https://packagist.org/packages/rupadana/filament-api-service) +![Fix Code](https://github.com/rupadana/filament-api-service/actions/workflows/fix-php-code-styling.yml/badge.svg?branch=main) +![Run Test](https://github.com/rupadana/filament-api-service/actions/workflows/run-tests.yml/badge.svg?branch=main) + ## Installation From e1ec702f6bc7211355c14ff2e7182c50b414cf4b Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 14:19:16 +0800 Subject: [PATCH 10/31] auto detect routes --- routes/api.php | 26 ++++ src/ApiService.php | 13 +- src/ApiServicePlugin.php | 58 +++++++- src/ApiServiceServiceProvider.php | 10 +- src/Commands/MakeApiHandlerCommand.php | 2 +- src/Commands/MakeApiServiceCommand.php | 2 +- src/Http/Handlers.php | 29 +++- src/Models/Token.php | 14 ++ src/Resources/TokenResource.php | 124 ++++++++++++++++++ .../TokenResource/Pages/CreateToken.php | 53 ++++++++ .../TokenResource/Pages/EditToken.php | 19 +++ .../TokenResource/Pages/ListTokens.php | 20 +++ stubs/ResourceApiService.stub | 15 ++- 13 files changed, 368 insertions(+), 17 deletions(-) create mode 100644 routes/api.php create mode 100644 src/Models/Token.php create mode 100644 src/Resources/TokenResource.php create mode 100644 src/Resources/TokenResource/Pages/CreateToken.php create mode 100644 src/Resources/TokenResource/Pages/EditToken.php create mode 100644 src/Resources/TokenResource/Pages/ListTokens.php diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..38a437e --- /dev/null +++ b/routes/api.php @@ -0,0 +1,26 @@ +name('api-service.') + ->middleware('auth:sanctum') + ->group(function () { + $panels = Filament::getPanels(); + + foreach ($panels as $key => $panel) { + try { + + Route::prefix($panel->getId()) + ->name($panel->getId() . '.') + ->group(function () use ($panel) { + $apiServicePlugin = $panel->getPlugin('api-service'); + $apiServicePlugin->route($panel); + }); + + } catch (Exception $e) { + // dd($e); + } + } + }); diff --git a/src/ApiService.php b/src/ApiService.php index 8c1509f..9de5383 100644 --- a/src/ApiService.php +++ b/src/ApiService.php @@ -30,7 +30,7 @@ public static function getResource() return static::$resource; } - public static function routes() + public static function registerRoutes() { $slug = static::getResource()::getSlug(); @@ -43,12 +43,17 @@ public static function routes() $name ) ->prefix(static::$groupRouteName ?? $slug) - ->group(function (Router $router) { - static::allRoutes($router); + ->group(function (Router $route) { + static::handlers(); + + foreach (static::handlers() as $key => $handler) { + app($handler)->route($route); + } }); } - public static function allRoutes(Router $router) + public static function handlers() : array { + return []; } } diff --git a/src/ApiServicePlugin.php b/src/ApiServicePlugin.php index 0bc319e..6681e1d 100644 --- a/src/ApiServicePlugin.php +++ b/src/ApiServicePlugin.php @@ -2,11 +2,21 @@ namespace Rupadana\ApiService; +use Exception; use Filament\Contracts\Plugin; use Filament\Panel; +use Filament\Support\Commands\Concerns\CanManipulateFiles; +use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Routing\Router; +use Illuminate\Support\Facades\Route; +use Illuminate\Support\Str; +use Rupadana\ApiService\Resources\TokenResource; class ApiServicePlugin implements Plugin { + + use CanManipulateFiles; + public function getId(): string { return 'api-service'; @@ -14,12 +24,56 @@ public function getId(): string public function register(Panel $panel): void { - // + $panel->resources([ + TokenResource::class + ]); } public function boot(Panel $panel): void { - // + } + + public static function getAbilities(Panel $panel): array + { + $resources = $panel->getResources(); + + $abilities = []; + foreach ($resources as $key => $resource) { + try { + + $resourceName = str($resource)->beforeLast('Resource')->explode('\\')->last(); + + $apiServiceClass = $resource . '\\Api\\' . $resourceName . 'ApiService'; + + $handlers = app($apiServiceClass)->handlers(); + + if (count($handlers) > 0) { + $abilities[$resource] = []; + foreach ($handlers as $key => $handler) { + $abilities[$resource][$handler] = app($handler)->getAbility(); + } + } + } catch (Exception $e) { + } + } + + return $abilities; + } + + public function route(Panel $panel): void + { + $resources = $panel->getResources(); + + foreach ($resources as $key => $resource) { + try { + $resourceName = str($resource)->beforeLast('Resource')->explode('\\')->last(); + + $apiServiceClass = $resource . '\\Api\\' . $resourceName . 'ApiService'; + + app($apiServiceClass)->registerRoutes(); + } catch (Exception $e) { + } + } } public static function make(): static diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index 15793e1..cbd750d 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -6,6 +6,8 @@ use Filament\Support\Facades\FilamentAsset; use Filament\Support\Facades\FilamentIcon; use Illuminate\Filesystem\Filesystem; +use Laravel\Sanctum\Http\Middleware\CheckAbilities; +use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; use Rupadana\ApiService\Commands\MakeApiHandlerCommand; use Rupadana\ApiService\Commands\MakeApiServiceCommand; use Rupadana\ApiService\Commands\MakeApiTransformerCommand; @@ -34,7 +36,8 @@ public function configurePackage(Package $package): void ->publishMigrations() ->askToRunMigrations() ->askToStarRepoOnGitHub('rupadana/api-service'); - }); + }) + ->hasRoute('api'); $configFileName = $package->shortName(); @@ -83,6 +86,11 @@ public function packageBooted(): void ], 'api-service-stubs'); } } + + + $router = app('router'); + $router->aliasMiddleware('abilities', CheckAbilities::class); + $router->aliasMiddleware('ability', CheckForAnyAbility::class); } protected function getAssetPackageName(): ?string diff --git a/src/Commands/MakeApiHandlerCommand.php b/src/Commands/MakeApiHandlerCommand.php index 124d038..d0bec54 100644 --- a/src/Commands/MakeApiHandlerCommand.php +++ b/src/Commands/MakeApiHandlerCommand.php @@ -120,7 +120,7 @@ public function handle(): int ]); $this->components->info("Successfully created API Handler for {$resource}!"); - $this->components->info("You can register \"Handlers\\$handlerClass::route(\$router);\" to allRoutes method on APIService"); + $this->components->info("You can register \"Handlers\\$handlerClass::class\" to handlers method on APIService"); return static::SUCCESS; } diff --git a/src/Commands/MakeApiServiceCommand.php b/src/Commands/MakeApiServiceCommand.php index b450efa..880495b 100644 --- a/src/Commands/MakeApiServiceCommand.php +++ b/src/Commands/MakeApiServiceCommand.php @@ -143,7 +143,7 @@ public function handle(): int ]); $this->components->info("Successfully created API for {$resource}!"); - $this->components->info("Add \" $apiServiceClass::routes() \" to routes/api.php"); + $this->components->info("It automatically registered to '/api-service' route group"); return static::SUCCESS; } diff --git a/src/Http/Handlers.php b/src/Http/Handlers.php index 55bd943..a3e7356 100644 --- a/src/Http/Handlers.php +++ b/src/Http/Handlers.php @@ -37,7 +37,32 @@ public static function route(Router $router) { $method = static::getMethod(); - $router->$method(static::$uri, [static::class, 'handler']); + $router + ->$method(static::$uri, [static::class, 'handler']) + ->name(static::getKebabClassName()) + ->middleware(static::getMiddlewareAliasName() . ':' . static::stringifyAbility()); + } + + + protected static function getMiddlewareAliasName() + { + return 'ability'; + } + + public static function getKebabClassName() + { + return str(str(static::class)->beforeLast('Handler')->explode('\\')->last())->kebab(); + } + + public static function stringifyAbility() { + return implode(',', static::getAbility()); + } + + public static function getAbility(): array + { + return [ + str(str(static::getModel())->explode('\\')->last())->kebab() . ':' . static::getKebabClassName(), + ]; } public static function getModel() @@ -47,7 +72,7 @@ public static function getModel() public static function getApiTransformer(): ?string { - if (! method_exists(static::$resource, 'getApiTransformer')) { + if (!method_exists(static::$resource, 'getApiTransformer')) { return DefaultTransformer::class; } diff --git a/src/Models/Token.php b/src/Models/Token.php new file mode 100644 index 0000000..472c81c --- /dev/null +++ b/src/Models/Token.php @@ -0,0 +1,14 @@ +schema([ + Section::make('General') + ->schema([ + TextInput::make('name') + ->required(), + Select::make('tokenable_id') + ->options(User::all()->pluck('name', 'id')) + ->label('User') + ->required() + ]), + + Section::make('Abilities') + ->description('Select abilities of the token') + ->schema(static::getAbilitiesSchema()) + ]); + } + + public static function getAbilitiesSchema(): array + { + $schema = []; + + $abilities = ApiServicePlugin::getAbilities(Filament::getCurrentPanel()); + + foreach ($abilities as $resource => $handler) { + $extractedAbilities = []; + foreach ($handler as $handlerClass => $ability) { + foreach ($ability as $a) { + $extractedAbilities[$a] = $a; + } + } + $schema[] = Section::make(str($resource)->beforeLast('Resource')->explode('\\')->last()) + ->description($resource) + ->schema([ + CheckboxList::make('ability') + ->options($extractedAbilities) + ->selectAllAction(fn (Action $action) => $action->label("Select all")) + ->deselectAllAction(fn (Action $action) => $action->label("Unselect All")) + ->bulkToggleable(), + ]) + ->collapsible(); + } + + + return $schema; + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('name'), + TextColumn::make('tokenable.name') + ->label('User'), + TextColumn::make('abilities') + ->badge() + ->words(1), + TextColumn::make('created_at') + ->toggleable(isToggledHiddenByDefault: true) + ]) + ->filters([ + // + ]) + ->actions([ + DeleteAction::make() + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListTokens::route('/'), + 'create' => Pages\CreateToken::route('/create') + ]; + } + + + public static function getNavigationGroup(): ?string + { + return 'User'; + } +} diff --git a/src/Resources/TokenResource/Pages/CreateToken.php b/src/Resources/TokenResource/Pages/CreateToken.php new file mode 100644 index 0000000..76a2f16 --- /dev/null +++ b/src/Resources/TokenResource/Pages/CreateToken.php @@ -0,0 +1,53 @@ +createToken($data['name'], $data['ability']); + + Notification::make() + ->title('Token created, save it!') + ->body($newToken->plainTextToken) + ->persistent() + ->actions([ + Action::make('close') + ->close() + ]) + ->success() + ->send(); + return $user; + } + + protected function sendCreatedNotificationAndRedirect(bool $shouldCreateAnotherInsteadOfRedirecting = true): void + { + if ($shouldCreateAnotherInsteadOfRedirecting) { + // Ensure that the form record is anonymized so that relationships aren't loaded. + $this->form->model($this->getRecord()::class); + $this->record = null; + + $this->fillForm(); + + return; + } + + $this->redirect($this->getRedirectUrl()); + } +} diff --git a/src/Resources/TokenResource/Pages/EditToken.php b/src/Resources/TokenResource/Pages/EditToken.php new file mode 100644 index 0000000..0c044eb --- /dev/null +++ b/src/Resources/TokenResource/Pages/EditToken.php @@ -0,0 +1,19 @@ + Date: Thu, 11 Jan 2024 06:19:42 +0000 Subject: [PATCH 11/31] Fix styling --- routes/api.php | 2 +- src/ApiService.php | 2 +- src/ApiServicePlugin.php | 11 +++-------- src/ApiServiceServiceProvider.php | 1 - src/Http/Handlers.php | 10 +++++----- src/Models/Token.php | 1 - src/Resources/TokenResource.php | 19 ++++++++----------- .../TokenResource/Pages/CreateToken.php | 8 +++----- .../TokenResource/Pages/EditToken.php | 2 +- .../TokenResource/Pages/ListTokens.php | 1 - 10 files changed, 22 insertions(+), 35 deletions(-) diff --git a/routes/api.php b/routes/api.php index 38a437e..1429b21 100644 --- a/routes/api.php +++ b/routes/api.php @@ -13,7 +13,7 @@ try { Route::prefix($panel->getId()) - ->name($panel->getId() . '.') + ->name($panel->getId().'.') ->group(function () use ($panel) { $apiServicePlugin = $panel->getPlugin('api-service'); $apiServicePlugin->route($panel); diff --git a/src/ApiService.php b/src/ApiService.php index 9de5383..a5a1e53 100644 --- a/src/ApiService.php +++ b/src/ApiService.php @@ -52,7 +52,7 @@ public static function registerRoutes() }); } - public static function handlers() : array + public static function handlers(): array { return []; } diff --git a/src/ApiServicePlugin.php b/src/ApiServicePlugin.php index 6681e1d..dac91f1 100644 --- a/src/ApiServicePlugin.php +++ b/src/ApiServicePlugin.php @@ -6,15 +6,10 @@ use Filament\Contracts\Plugin; use Filament\Panel; use Filament\Support\Commands\Concerns\CanManipulateFiles; -use Illuminate\Contracts\Container\BindingResolutionException; -use Illuminate\Routing\Router; -use Illuminate\Support\Facades\Route; -use Illuminate\Support\Str; use Rupadana\ApiService\Resources\TokenResource; class ApiServicePlugin implements Plugin { - use CanManipulateFiles; public function getId(): string @@ -25,7 +20,7 @@ public function getId(): string public function register(Panel $panel): void { $panel->resources([ - TokenResource::class + TokenResource::class, ]); } @@ -43,7 +38,7 @@ public static function getAbilities(Panel $panel): array $resourceName = str($resource)->beforeLast('Resource')->explode('\\')->last(); - $apiServiceClass = $resource . '\\Api\\' . $resourceName . 'ApiService'; + $apiServiceClass = $resource.'\\Api\\'.$resourceName.'ApiService'; $handlers = app($apiServiceClass)->handlers(); @@ -68,7 +63,7 @@ public function route(Panel $panel): void try { $resourceName = str($resource)->beforeLast('Resource')->explode('\\')->last(); - $apiServiceClass = $resource . '\\Api\\' . $resourceName . 'ApiService'; + $apiServiceClass = $resource.'\\Api\\'.$resourceName.'ApiService'; app($apiServiceClass)->registerRoutes(); } catch (Exception $e) { diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index cbd750d..5633c1d 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -87,7 +87,6 @@ public function packageBooted(): void } } - $router = app('router'); $router->aliasMiddleware('abilities', CheckAbilities::class); $router->aliasMiddleware('ability', CheckForAnyAbility::class); diff --git a/src/Http/Handlers.php b/src/Http/Handlers.php index a3e7356..87b10f3 100644 --- a/src/Http/Handlers.php +++ b/src/Http/Handlers.php @@ -40,10 +40,9 @@ public static function route(Router $router) $router ->$method(static::$uri, [static::class, 'handler']) ->name(static::getKebabClassName()) - ->middleware(static::getMiddlewareAliasName() . ':' . static::stringifyAbility()); + ->middleware(static::getMiddlewareAliasName().':'.static::stringifyAbility()); } - protected static function getMiddlewareAliasName() { return 'ability'; @@ -54,14 +53,15 @@ public static function getKebabClassName() return str(str(static::class)->beforeLast('Handler')->explode('\\')->last())->kebab(); } - public static function stringifyAbility() { + public static function stringifyAbility() + { return implode(',', static::getAbility()); } public static function getAbility(): array { return [ - str(str(static::getModel())->explode('\\')->last())->kebab() . ':' . static::getKebabClassName(), + str(str(static::getModel())->explode('\\')->last())->kebab().':'.static::getKebabClassName(), ]; } @@ -72,7 +72,7 @@ public static function getModel() public static function getApiTransformer(): ?string { - if (!method_exists(static::$resource, 'getApiTransformer')) { + if (! method_exists(static::$resource, 'getApiTransformer')) { return DefaultTransformer::class; } diff --git a/src/Models/Token.php b/src/Models/Token.php index 472c81c..389c151 100644 --- a/src/Models/Token.php +++ b/src/Models/Token.php @@ -3,7 +3,6 @@ namespace Rupadana\ApiService\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Laravel\Sanctum\PersonalAccessToken; class Token extends PersonalAccessToken diff --git a/src/Resources/TokenResource.php b/src/Resources/TokenResource.php index 3d2d0b2..d1b0c76 100644 --- a/src/Resources/TokenResource.php +++ b/src/Resources/TokenResource.php @@ -2,8 +2,6 @@ namespace Rupadana\ApiService\Resources; - -use Rupadana\ApiService\Resources\TokenResource\Pages; use App\Models\User; use Filament\Facades\Filament; use Filament\Forms\Components\Actions\Action; @@ -19,6 +17,7 @@ use Filament\Tables\Table; use Rupadana\ApiService\ApiServicePlugin; use Rupadana\ApiService\Models\Token; +use Rupadana\ApiService\Resources\TokenResource\Pages; class TokenResource extends Resource { @@ -37,12 +36,12 @@ public static function form(Form $form): Form Select::make('tokenable_id') ->options(User::all()->pluck('name', 'id')) ->label('User') - ->required() + ->required(), ]), Section::make('Abilities') ->description('Select abilities of the token') - ->schema(static::getAbilitiesSchema()) + ->schema(static::getAbilitiesSchema()), ]); } @@ -64,14 +63,13 @@ public static function getAbilitiesSchema(): array ->schema([ CheckboxList::make('ability') ->options($extractedAbilities) - ->selectAllAction(fn (Action $action) => $action->label("Select all")) - ->deselectAllAction(fn (Action $action) => $action->label("Unselect All")) + ->selectAllAction(fn (Action $action) => $action->label('Select all')) + ->deselectAllAction(fn (Action $action) => $action->label('Unselect All')) ->bulkToggleable(), ]) ->collapsible(); } - return $schema; } @@ -86,13 +84,13 @@ public static function table(Table $table): Table ->badge() ->words(1), TextColumn::make('created_at') - ->toggleable(isToggledHiddenByDefault: true) + ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ // ]) ->actions([ - DeleteAction::make() + DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ @@ -112,11 +110,10 @@ public static function getPages(): array { return [ 'index' => Pages\ListTokens::route('/'), - 'create' => Pages\CreateToken::route('/create') + 'create' => Pages\CreateToken::route('/create'), ]; } - public static function getNavigationGroup(): ?string { return 'User'; diff --git a/src/Resources/TokenResource/Pages/CreateToken.php b/src/Resources/TokenResource/Pages/CreateToken.php index 76a2f16..a8755e7 100644 --- a/src/Resources/TokenResource/Pages/CreateToken.php +++ b/src/Resources/TokenResource/Pages/CreateToken.php @@ -2,15 +2,12 @@ namespace Rupadana\ApiService\Resources\TokenResource\Pages; - -use Rupadana\ApiService\Resources\TokenResource; use App\Models\User; -use Filament\Actions; -use Filament\Facades\Filament; use Filament\Notifications\Actions\Action; use Filament\Notifications\Notification; use Filament\Resources\Pages\CreateRecord; use Illuminate\Database\Eloquent\Model; +use Rupadana\ApiService\Resources\TokenResource; class CreateToken extends CreateRecord { @@ -29,10 +26,11 @@ protected function handleRecordCreation(array $data): Model ->persistent() ->actions([ Action::make('close') - ->close() + ->close(), ]) ->success() ->send(); + return $user; } diff --git a/src/Resources/TokenResource/Pages/EditToken.php b/src/Resources/TokenResource/Pages/EditToken.php index 0c044eb..885f7f9 100644 --- a/src/Resources/TokenResource/Pages/EditToken.php +++ b/src/Resources/TokenResource/Pages/EditToken.php @@ -2,9 +2,9 @@ namespace Rupadana\ApiService\Resources\TokenResource\Pages; -use Rupadana\ApiService\Resources\TokenResource; use Filament\Actions; use Filament\Resources\Pages\EditRecord; +use Rupadana\ApiService\Resources\TokenResource; class EditToken extends EditRecord { diff --git a/src/Resources/TokenResource/Pages/ListTokens.php b/src/Resources/TokenResource/Pages/ListTokens.php index e7fac49..62c0ecd 100644 --- a/src/Resources/TokenResource/Pages/ListTokens.php +++ b/src/Resources/TokenResource/Pages/ListTokens.php @@ -2,7 +2,6 @@ namespace Rupadana\ApiService\Resources\TokenResource\Pages; - use Filament\Actions; use Filament\Resources\Pages\ListRecords; use Rupadana\ApiService\Resources\TokenResource; From 0d029494cf09bbdcc262d885bb160917756da72a Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 15:23:26 +0800 Subject: [PATCH 12/31] update test case --- composer.json | 4 +- routes/api.php | 3 +- src/ApiServicePlugin.php | 4 +- src/ApiServiceServiceProvider.php | 1 + tests/ApiServiceTest.php | 83 +++++++++++++++---- ...01_create_personal_access_tokens_table.php | 37 +++++++++ .../Fixtures/Database/Seeders/UserSeeder.php | 20 +++++ tests/Fixtures/Models/User.php | 48 +++++++++++ .../ProductApiService/ProductApiService.php | 22 ----- .../Fixtures/Providers/AdminPanelProvider.php | 59 +++++++++++++ .../Api}/Handlers/CreateHandler.php | 2 +- .../Api}/Handlers/DeleteHandler.php | 2 +- .../Api}/Handlers/DetailHandler.php | 2 +- .../Api}/Handlers/PaginationHandler.php | 2 +- .../Api}/Handlers/UpdateHandler.php | 2 +- .../ProductResource/Api/ProductApiService.php | 25 ++++++ tests/TestCase.php | 10 ++- 17 files changed, 278 insertions(+), 48 deletions(-) create mode 100644 tests/Fixtures/Database/Migrations/2019_12_14_000001_create_personal_access_tokens_table.php create mode 100644 tests/Fixtures/Database/Seeders/UserSeeder.php create mode 100644 tests/Fixtures/Models/User.php delete mode 100644 tests/Fixtures/ProductApiService/ProductApiService.php create mode 100644 tests/Fixtures/Providers/AdminPanelProvider.php rename tests/Fixtures/{ProductApiService => Resources/ProductResource/Api}/Handlers/CreateHandler.php (89%) rename tests/Fixtures/{ProductApiService => Resources/ProductResource/Api}/Handlers/DeleteHandler.php (90%) rename tests/Fixtures/{ProductApiService => Resources/ProductResource/Api}/Handlers/DetailHandler.php (89%) rename tests/Fixtures/{ProductApiService => Resources/ProductResource/Api}/Handlers/PaginationHandler.php (89%) rename tests/Fixtures/{ProductApiService => Resources/ProductResource/Api}/Handlers/UpdateHandler.php (90%) create mode 100644 tests/Fixtures/Resources/ProductResource/Api/ProductApiService.php diff --git a/composer.json b/composer.json index a64dbfb..9f6f91a 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,8 @@ ], "require": { "php": "^8.1", + "laravel/framework": "^10.10", + "laravel/sanctum": "^3.2", "filament/filament": "^3.0", "spatie/laravel-package-tools": "^1.14.0", "illuminate/contracts": "^10.0", @@ -68,4 +70,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 1429b21..627efff 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,6 +3,7 @@ use Filament\Facades\Filament; use Illuminate\Support\Facades\Route; + Route::prefix('api-service') ->name('api-service.') ->middleware('auth:sanctum') @@ -20,7 +21,7 @@ }); } catch (Exception $e) { - // dd($e); + dd($e); } } }); diff --git a/src/ApiServicePlugin.php b/src/ApiServicePlugin.php index dac91f1..57228d8 100644 --- a/src/ApiServicePlugin.php +++ b/src/ApiServicePlugin.php @@ -63,8 +63,8 @@ public function route(Panel $panel): void try { $resourceName = str($resource)->beforeLast('Resource')->explode('\\')->last(); - $apiServiceClass = $resource.'\\Api\\'.$resourceName.'ApiService'; - + $apiServiceClass = $resource . '\\Api\\' . $resourceName . 'ApiService'; + app($apiServiceClass)->registerRoutes(); } catch (Exception $e) { } diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index 5633c1d..ee0fcc2 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -8,6 +8,7 @@ use Illuminate\Filesystem\Filesystem; use Laravel\Sanctum\Http\Middleware\CheckAbilities; use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; +use Laravel\Sanctum\Sanctum; use Rupadana\ApiService\Commands\MakeApiHandlerCommand; use Rupadana\ApiService\Commands\MakeApiServiceCommand; use Rupadana\ApiService\Commands\MakeApiTransformerCommand; diff --git a/tests/ApiServiceTest.php b/tests/ApiServiceTest.php index 716ecad..6fd7002 100644 --- a/tests/ApiServiceTest.php +++ b/tests/ApiServiceTest.php @@ -2,25 +2,38 @@ use Illuminate\Routing\Route as RoutingRoute; use Rupadana\ApiService\Tests\Fixtures\Database\Seeders\ProductsSeeder; +use Rupadana\ApiService\Tests\Fixtures\Database\Seeders\UserSeeder; use Rupadana\ApiService\Tests\Fixtures\Models\Product; +use Rupadana\ApiService\Tests\Fixtures\Models\User; it('can make routes for a product resource', function () { $routes = collect(app('router')->getRoutes())->map(function (RoutingRoute $route) { - return implode('|', $route->methods()).' '.$route->uri(); + return implode('|', $route->methods()) . ' ' . $route->uri(); }); + + + // The route name is customized to `our-products` in the `ProductApiService` class - expect($routes)->toContain('POST api/our-products'); - expect($routes)->toContain('PUT api/our-products/{id}'); - expect($routes)->toContain('DELETE api/our-products/{id}'); - expect($routes)->toContain('GET|HEAD api/our-products'); - expect($routes)->toContain('GET|HEAD api/our-products/{id}'); + expect($routes)->toContain('POST api-service/admin/our-products'); + expect($routes)->toContain('PUT api-service/admin/our-products/{id}'); + expect($routes)->toContain('DELETE api-service/admin/our-products/{id}'); + expect($routes)->toContain('GET|HEAD api-service/admin/our-products'); + expect($routes)->toContain('GET|HEAD api-service/admin/our-products/{id}'); }); + + it('can return a list of products with allowed attributes', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); + + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api/our-products') + $response = $this->get('/api-service/admin/our-products', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(200); $products = Product::all()->map(function ($product) { @@ -43,8 +56,14 @@ it('can return a list of products with selected fields', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); + + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api/our-products?fields[products]=name,price') + $response = $this->get('/api-service/admin/our-products?fields[products]=name,price', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(200); $response->assertJsonFragment([ @@ -55,8 +74,14 @@ it('throws when selecting a field that is not allowed', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); - $this->get('/api/our-products?fields[products]=name,slug,price') + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; + + $this->get('/api-service/admin/our-products?fields[products]=name,slug,price', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(400) ->assertJsonFragment([ 'message' => 'Requested field(s) `products.slug` are not allowed.', @@ -65,8 +90,14 @@ it('can return a list of products with selected sorts', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); + + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api/our-products?sort=-price') + $response = $this->get('/api-service/admin/our-products?sort=-price', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(200); $data = Product::all() @@ -87,8 +118,14 @@ it('throws when sorting by a field that is not allowed', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); + + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; - $this->get('/api/our-products?sort=-slug') + $this->get('/api-service/admin/our-products?sort=-slug', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(400) ->assertJsonFragment([ 'message' => 'Requested sort(s) `products.slug` are not allowed.', @@ -97,8 +134,14 @@ it('can return a list of products with selected filters', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); - $response = $this->get('/api/our-products?filter[name]=T-Shirt&filter[price]=500') + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; + + $response = $this->get('/api-service/admin/our-products?filter[name]=T-Shirt&filter[price]=500', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(200); $response->assertJsonFragment([ @@ -109,8 +152,14 @@ it('throws when filtering by a field that is not allowed', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); + + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; - $this->get('/api/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt') + $this->get('/api-service/admin/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(400) ->assertJsonFragment([ 'message' => 'Requested filter(s) `products.slug` are not allowed.', @@ -119,8 +168,14 @@ it('can return a list of products with a custom transformer', function () { $this->seed(ProductsSeeder::class); + $this->seed(UserSeeder::class); + + $user = User::find(1); + $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api/our-products') + $response = $this->get('/api-service/admin/our-products', [ + 'Authorization' => 'Bearer ' . $token + ]) ->assertStatus(200); $product = Product::first(); diff --git a/tests/Fixtures/Database/Migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/tests/Fixtures/Database/Migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 0000000..6c81fd2 --- /dev/null +++ b/tests/Fixtures/Database/Migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,37 @@ +id(); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/tests/Fixtures/Database/Seeders/UserSeeder.php b/tests/Fixtures/Database/Seeders/UserSeeder.php new file mode 100644 index 0000000..1444c01 --- /dev/null +++ b/tests/Fixtures/Database/Seeders/UserSeeder.php @@ -0,0 +1,20 @@ + 'Rupadana', + 'email' => 'rupadanawayan@gmail.com', + 'password' => bcrypt('12345678') + ]); + } +} diff --git a/tests/Fixtures/Models/User.php b/tests/Fixtures/Models/User.php new file mode 100644 index 0000000..044e966 --- /dev/null +++ b/tests/Fixtures/Models/User.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + ]; + + public function canAccessPanel(Panel $panel): bool + { + return true; + } +} diff --git a/tests/Fixtures/ProductApiService/ProductApiService.php b/tests/Fixtures/ProductApiService/ProductApiService.php deleted file mode 100644 index f5d5c55..0000000 --- a/tests/Fixtures/ProductApiService/ProductApiService.php +++ /dev/null @@ -1,22 +0,0 @@ -default() + ->id('admin') + ->path('admin') + ->login() + ->colors([ + 'primary' => Color::Amber, + ]) + ->plugins([ + ApiServicePlugin::make(), + ]) + ->resources([ + ProductResource::class + ]) + ->widgets([ + Widgets\AccountWidget::class, + ]) + ->middleware([ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + DisableBladeIconComponents::class, + DispatchServingFilamentEvent::class, + ]) + ->authMiddleware([ + Authenticate::class, + ]); + } +} diff --git a/tests/Fixtures/ProductApiService/Handlers/CreateHandler.php b/tests/Fixtures/Resources/ProductResource/Api/Handlers/CreateHandler.php similarity index 89% rename from tests/Fixtures/ProductApiService/Handlers/CreateHandler.php rename to tests/Fixtures/Resources/ProductResource/Api/Handlers/CreateHandler.php index fb3a547..6734acd 100644 --- a/tests/Fixtures/ProductApiService/Handlers/CreateHandler.php +++ b/tests/Fixtures/Resources/ProductResource/Api/Handlers/CreateHandler.php @@ -1,6 +1,6 @@ group(['prefix' => 'api'], function () { - ProductApiService::routes(); - }); + // $router->group(['prefix' => 'api'], function () { + // ProductApiService::routes(); + // }); } } From 8a58b5c23dfd800fe50279aaa30dfe3f71cd5fbf Mon Sep 17 00:00:00 2001 From: rupadana Date: Thu, 11 Jan 2024 07:31:14 +0000 Subject: [PATCH 13/31] Fix styling --- routes/api.php | 1 - src/ApiServicePlugin.php | 4 ++-- src/ApiServiceServiceProvider.php | 1 - tests/ApiServiceTest.php | 23 ++++++++----------- .../Fixtures/Database/Seeders/UserSeeder.php | 4 +--- tests/Fixtures/Models/User.php | 2 +- .../Fixtures/Providers/AdminPanelProvider.php | 3 +-- .../ProductResource/Api/ProductApiService.php | 3 +-- 8 files changed, 15 insertions(+), 26 deletions(-) diff --git a/routes/api.php b/routes/api.php index 627efff..18c7c11 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,7 +3,6 @@ use Filament\Facades\Filament; use Illuminate\Support\Facades\Route; - Route::prefix('api-service') ->name('api-service.') ->middleware('auth:sanctum') diff --git a/src/ApiServicePlugin.php b/src/ApiServicePlugin.php index 57228d8..dac91f1 100644 --- a/src/ApiServicePlugin.php +++ b/src/ApiServicePlugin.php @@ -63,8 +63,8 @@ public function route(Panel $panel): void try { $resourceName = str($resource)->beforeLast('Resource')->explode('\\')->last(); - $apiServiceClass = $resource . '\\Api\\' . $resourceName . 'ApiService'; - + $apiServiceClass = $resource.'\\Api\\'.$resourceName.'ApiService'; + app($apiServiceClass)->registerRoutes(); } catch (Exception $e) { } diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index ee0fcc2..5633c1d 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -8,7 +8,6 @@ use Illuminate\Filesystem\Filesystem; use Laravel\Sanctum\Http\Middleware\CheckAbilities; use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; -use Laravel\Sanctum\Sanctum; use Rupadana\ApiService\Commands\MakeApiHandlerCommand; use Rupadana\ApiService\Commands\MakeApiServiceCommand; use Rupadana\ApiService\Commands\MakeApiTransformerCommand; diff --git a/tests/ApiServiceTest.php b/tests/ApiServiceTest.php index 6fd7002..f8cd043 100644 --- a/tests/ApiServiceTest.php +++ b/tests/ApiServiceTest.php @@ -8,12 +8,9 @@ it('can make routes for a product resource', function () { $routes = collect(app('router')->getRoutes())->map(function (RoutingRoute $route) { - return implode('|', $route->methods()) . ' ' . $route->uri(); + return implode('|', $route->methods()).' '.$route->uri(); }); - - - // The route name is customized to `our-products` in the `ProductApiService` class expect($routes)->toContain('POST api-service/admin/our-products'); expect($routes)->toContain('PUT api-service/admin/our-products/{id}'); @@ -22,8 +19,6 @@ expect($routes)->toContain('GET|HEAD api-service/admin/our-products/{id}'); }); - - it('can return a list of products with allowed attributes', function () { $this->seed(ProductsSeeder::class); $this->seed(UserSeeder::class); @@ -32,7 +27,7 @@ $token = $user->createToken('testing')->plainTextToken; $response = $this->get('/api-service/admin/our-products', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -62,7 +57,7 @@ $token = $user->createToken('testing')->plainTextToken; $response = $this->get('/api-service/admin/our-products?fields[products]=name,price', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -80,7 +75,7 @@ $token = $user->createToken('testing')->plainTextToken; $this->get('/api-service/admin/our-products?fields[products]=name,slug,price', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(400) ->assertJsonFragment([ @@ -96,7 +91,7 @@ $token = $user->createToken('testing')->plainTextToken; $response = $this->get('/api-service/admin/our-products?sort=-price', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -124,7 +119,7 @@ $token = $user->createToken('testing')->plainTextToken; $this->get('/api-service/admin/our-products?sort=-slug', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(400) ->assertJsonFragment([ @@ -140,7 +135,7 @@ $token = $user->createToken('testing')->plainTextToken; $response = $this->get('/api-service/admin/our-products?filter[name]=T-Shirt&filter[price]=500', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -158,7 +153,7 @@ $token = $user->createToken('testing')->plainTextToken; $this->get('/api-service/admin/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(400) ->assertJsonFragment([ @@ -174,7 +169,7 @@ $token = $user->createToken('testing')->plainTextToken; $response = $this->get('/api-service/admin/our-products', [ - 'Authorization' => 'Bearer ' . $token + 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); diff --git a/tests/Fixtures/Database/Seeders/UserSeeder.php b/tests/Fixtures/Database/Seeders/UserSeeder.php index 1444c01..69f0396 100644 --- a/tests/Fixtures/Database/Seeders/UserSeeder.php +++ b/tests/Fixtures/Database/Seeders/UserSeeder.php @@ -3,8 +3,6 @@ namespace Rupadana\ApiService\Tests\Fixtures\Database\Seeders; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\DB; -use Rupadana\ApiService\Tests\Fixtures\Models\Product; use Rupadana\ApiService\Tests\Fixtures\Models\User; class UserSeeder extends Seeder @@ -14,7 +12,7 @@ public function run() User::create([ 'name' => 'Rupadana', 'email' => 'rupadanawayan@gmail.com', - 'password' => bcrypt('12345678') + 'password' => bcrypt('12345678'), ]); } } diff --git a/tests/Fixtures/Models/User.php b/tests/Fixtures/Models/User.php index 044e966..3c97c2d 100644 --- a/tests/Fixtures/Models/User.php +++ b/tests/Fixtures/Models/User.php @@ -4,13 +4,13 @@ use Filament\Models\Contracts\FilamentUser; use Filament\Panel; -use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable implements FilamentUser { use HasApiTokens; + /** * The attributes that are mass assignable. * diff --git a/tests/Fixtures/Providers/AdminPanelProvider.php b/tests/Fixtures/Providers/AdminPanelProvider.php index e8351a0..cb6f8e5 100644 --- a/tests/Fixtures/Providers/AdminPanelProvider.php +++ b/tests/Fixtures/Providers/AdminPanelProvider.php @@ -5,7 +5,6 @@ use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DispatchServingFilamentEvent; -use Filament\Pages; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; @@ -36,7 +35,7 @@ public function panel(Panel $panel): Panel ApiServicePlugin::make(), ]) ->resources([ - ProductResource::class + ProductResource::class, ]) ->widgets([ Widgets\AccountWidget::class, diff --git a/tests/Fixtures/Resources/ProductResource/Api/ProductApiService.php b/tests/Fixtures/Resources/ProductResource/Api/ProductApiService.php index 0bf584e..93691dc 100644 --- a/tests/Fixtures/Resources/ProductResource/Api/ProductApiService.php +++ b/tests/Fixtures/Resources/ProductResource/Api/ProductApiService.php @@ -2,7 +2,6 @@ namespace Rupadana\ApiService\Tests\Fixtures\Resources\ProductResource\Api; -use Illuminate\Routing\Router; use Rupadana\ApiService\ApiService; class ProductApiService extends ApiService @@ -19,7 +18,7 @@ public static function handlers(): array Handlers\UpdateHandler::class, Handlers\DeleteHandler::class, Handlers\PaginationHandler::class, - Handlers\DetailHandler::class + Handlers\DetailHandler::class, ]; } } From 530e6d1599704c837cb1086fa62c1c35d4428078 Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 15:41:38 +0800 Subject: [PATCH 14/31] update readme --- README.md | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 6b56025..a62c698 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,19 @@ composer require rupadana/filament-api-service php artisan make:filament-api-service BlogResource ``` -Add this code to your routes file, example in routes/api.php +From version 3.0, routes automatically registered. it will grouped as '/api-service/`admin`'. `admin` is panelId. -```php -... -use App\Filament\Resources\BlogResource\Api; -... -BlogApiService::routes(); -``` -and then you will got this routes: +So, You don't need to register the routes manually. + +The routes will be : -- [GET] '/api/blogs' - Return LengthAwarePaginator -- [GET] '/api/blogs/1' - Return single resource -- [PUT] '/api/blogs/1' - Update resource -- [POST] '/api/blogs' - Create resource -- [DELETE] '/api/blogs/1' - Delete resource +- [GET] '/api-service/`admin`/blogs' - Return LengthAwarePaginator +- [GET] '/api-service/`admin`/blogs/1' - Return single resource +- [PUT] '/api-service/`admin`/blogs/1' - Update resource +- [POST] '/api-service/`admin`/blogs' - Create resource +- [DELETE] '/api-service/`admin`/blogs/1' - Delete resource On CreateHandler, you need to be create your custom request validation. @@ -148,15 +144,11 @@ You can edit prefix & group route name as you want, default this plugin use mode ## How to secure it? -Basically, when u register the ApiService to the `routes/api.php` you can group it using `sanctum` middleware, Whichis this is default api authentication by Laravel. [Read more](https://laravel.com/docs/10.x/sanctum) about laravel sanctum +From version 3.0, it will automatically detect routes and secure it using sanctum. -### Example +To Generate Token, you just need create it from admin panel. It will be Token Resource there. -```php -Route::middleware('auth:sanctum')->group(function() { - BlogApiService::routes(); -}); -``` +![Image](https://res.cloudinary.com/rupadana/image/upload/v1704958748/Screenshot_2024-01-11_at_15.37.55_ncpg8n.png) ## Changelog From e7793179a9e4afd0c587e44cced7f16635d17bb4 Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 15:44:25 +0800 Subject: [PATCH 15/31] update readme --- README.md | 12 ++++++------ routes/api.php | 4 ++-- tests/ApiServiceTest.php | 26 +++++++++++++------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index a62c698..6c30adc 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ composer require rupadana/filament-api-service php artisan make:filament-api-service BlogResource ``` -From version 3.0, routes automatically registered. it will grouped as '/api-service/`admin`'. `admin` is panelId. +From version 3.0, routes automatically registered. it will grouped as '/api/`admin`'. `admin` is panelId. @@ -28,11 +28,11 @@ So, You don't need to register the routes manually. The routes will be : -- [GET] '/api-service/`admin`/blogs' - Return LengthAwarePaginator -- [GET] '/api-service/`admin`/blogs/1' - Return single resource -- [PUT] '/api-service/`admin`/blogs/1' - Update resource -- [POST] '/api-service/`admin`/blogs' - Create resource -- [DELETE] '/api-service/`admin`/blogs/1' - Delete resource +- [GET] '/api/`admin`/blogs' - Return LengthAwarePaginator +- [GET] '/api/`admin`/blogs/1' - Return single resource +- [PUT] '/api/`admin`/blogs/1' - Update resource +- [POST] '/api/`admin`/blogs' - Create resource +- [DELETE] '/api/`admin`/blogs/1' - Delete resource On CreateHandler, you need to be create your custom request validation. diff --git a/routes/api.php b/routes/api.php index 18c7c11..d0aa60f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,8 +3,8 @@ use Filament\Facades\Filament; use Illuminate\Support\Facades\Route; -Route::prefix('api-service') - ->name('api-service.') +Route::prefix('api') + ->name('api.') ->middleware('auth:sanctum') ->group(function () { $panels = Filament::getPanels(); diff --git a/tests/ApiServiceTest.php b/tests/ApiServiceTest.php index f8cd043..ff21a01 100644 --- a/tests/ApiServiceTest.php +++ b/tests/ApiServiceTest.php @@ -12,11 +12,11 @@ }); // The route name is customized to `our-products` in the `ProductApiService` class - expect($routes)->toContain('POST api-service/admin/our-products'); - expect($routes)->toContain('PUT api-service/admin/our-products/{id}'); - expect($routes)->toContain('DELETE api-service/admin/our-products/{id}'); - expect($routes)->toContain('GET|HEAD api-service/admin/our-products'); - expect($routes)->toContain('GET|HEAD api-service/admin/our-products/{id}'); + expect($routes)->toContain('POST api/admin/our-products'); + expect($routes)->toContain('PUT api/admin/our-products/{id}'); + expect($routes)->toContain('DELETE api/admin/our-products/{id}'); + expect($routes)->toContain('GET|HEAD api/admin/our-products'); + expect($routes)->toContain('GET|HEAD api/admin/our-products/{id}'); }); it('can return a list of products with allowed attributes', function () { @@ -26,7 +26,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api-service/admin/our-products', [ + $response = $this->get('/api/admin/our-products', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -56,7 +56,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api-service/admin/our-products?fields[products]=name,price', [ + $response = $this->get('/api/admin/our-products?fields[products]=name,price', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -74,7 +74,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $this->get('/api-service/admin/our-products?fields[products]=name,slug,price', [ + $this->get('/api/admin/our-products?fields[products]=name,slug,price', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(400) @@ -90,7 +90,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api-service/admin/our-products?sort=-price', [ + $response = $this->get('/api/admin/our-products?sort=-price', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -118,7 +118,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $this->get('/api-service/admin/our-products?sort=-slug', [ + $this->get('/api/admin/our-products?sort=-slug', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(400) @@ -134,7 +134,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api-service/admin/our-products?filter[name]=T-Shirt&filter[price]=500', [ + $response = $this->get('/api/admin/our-products?filter[name]=T-Shirt&filter[price]=500', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); @@ -152,7 +152,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $this->get('/api-service/admin/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt', [ + $this->get('/api/admin/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(400) @@ -168,7 +168,7 @@ $user = User::find(1); $token = $user->createToken('testing')->plainTextToken; - $response = $this->get('/api-service/admin/our-products', [ + $response = $this->get('/api/admin/our-products', [ 'Authorization' => 'Bearer '.$token, ]) ->assertStatus(200); From 1fb8b7fd53c0843c3d812d789d42394884e7bc21 Mon Sep 17 00:00:00 2001 From: rupadana Date: Thu, 11 Jan 2024 08:04:03 +0000 Subject: [PATCH 16/31] Update CHANGELOG --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ee7893..3981d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to `api-service` will be documented in this file. +## 3.0.0 - 2024-01-11 + +### What's Changed + +* Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/rupadana/filament-api-service/pull/7 +* Bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/rupadana/filament-api-service/pull/9 +* Add test suite by @luttje in https://github.com/rupadana/filament-api-service/pull/10 +* Auto register routes by @rupadana in https://github.com/rupadana/filament-api-service/pull/14 + +### New Contributors + +* @luttje made their first contribution in https://github.com/rupadana/filament-api-service/pull/10 + +**Full Changelog**: https://github.com/rupadana/filament-api-service/compare/v1.0.3...3.0.0 + ## Add AllowedSorts - 2023-10-07 ### What's Changed From a1e231ccf2b0569e0d46c8d61842d07731e2103d Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 16:36:09 +0800 Subject: [PATCH 17/31] remove dd --- routes/api.php | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index d0aa60f..a299907 100644 --- a/routes/api.php +++ b/routes/api.php @@ -20,7 +20,6 @@ }); } catch (Exception $e) { - dd($e); } } }); From 676220561be77679e9e3fe5fe63f11407b7594e6 Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 16:37:46 +0800 Subject: [PATCH 18/31] update readme --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 6c30adc..aa72a3b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,17 @@ You can install the package via composer: composer require rupadana/filament-api-service ``` +Register it to your filament Provider + + +```php +use Rupadana\ApiService\ApiServicePlugin; + +$panel->plugins([ + ApiServicePlugin::make() +]) +``` + ## Usage ```bash From 403e5fca1dba29fd1c1d1a1704722e8a2d75898b Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Thu, 11 Jan 2024 17:03:11 +0800 Subject: [PATCH 19/31] add navigation group to config --- composer.json | 2 +- config/api-service.php | 6 +++++- src/ApiServiceServiceProvider.php | 4 +--- src/Resources/TokenResource.php | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 9f6f91a..711a72c 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "rupadana/api-service", + "name": "rupadana/filament-api-service", "description": "A simple api service for supporting filamentphp", "keywords": [ "rupadana", diff --git a/config/api-service.php b/config/api-service.php index 0766c22..6302501 100644 --- a/config/api-service.php +++ b/config/api-service.php @@ -2,5 +2,9 @@ // config for Rupadana/ApiService return [ - + 'navigation' => [ + 'group' => [ + 'token' => 'User' + ] + ] ]; diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index 5633c1d..ad58683 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -33,9 +33,7 @@ public function configurePackage(Package $package): void ->hasInstallCommand(function (InstallCommand $command) { $command ->publishConfigFile() - ->publishMigrations() - ->askToRunMigrations() - ->askToStarRepoOnGitHub('rupadana/api-service'); + ->askToStarRepoOnGitHub('rupadana/filament-api-service'); }) ->hasRoute('api'); diff --git a/src/Resources/TokenResource.php b/src/Resources/TokenResource.php index d1b0c76..55fdc76 100644 --- a/src/Resources/TokenResource.php +++ b/src/Resources/TokenResource.php @@ -116,6 +116,6 @@ public static function getPages(): array public static function getNavigationGroup(): ?string { - return 'User'; + return config('api-service.navigation.group.token'); } } From effbd22ea2439b921a8d2f23f7e66bbfceac13dd Mon Sep 17 00:00:00 2001 From: rupadana Date: Thu, 11 Jan 2024 09:03:35 +0000 Subject: [PATCH 20/31] Fix styling --- config/api-service.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/api-service.php b/config/api-service.php index 6302501..9a77c02 100644 --- a/config/api-service.php +++ b/config/api-service.php @@ -4,7 +4,7 @@ return [ 'navigation' => [ 'group' => [ - 'token' => 'User' - ] - ] + 'token' => 'User', + ], + ], ]; From ed686f05a79dfb83146e5b70ffda7c949abd808d Mon Sep 17 00:00:00 2001 From: rupadana Date: Sat, 27 Jan 2024 09:20:49 +0000 Subject: [PATCH 21/31] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3981d20..be0e0be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `api-service` will be documented in this file. +## 3.0.4 - 2024-01-27 + +**Full Changelog**: https://github.com/rupadana/filament-api-service/compare/3.0.1...3.0.4 + ## 3.0.0 - 2024-01-11 ### What's Changed From b174902b1479227967d6cbdb26e2d52f36897ad8 Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Sat, 27 Jan 2024 17:30:15 +0800 Subject: [PATCH 22/31] Update README.md --- .phpunit.cache/test-results | 1 + README.md | 14 ++++++++++++-- src/ApiServiceServiceProvider.php | 8 +++----- 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 .phpunit.cache/test-results diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results new file mode 100644 index 0000000..9491d5a --- /dev/null +++ b/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":"pest_2.32.2","defects":[],"times":{"P\\Tests\\ApiServiceTest::__pest_evaluable_it_can_return_a_list_of_products_with_selected_filters":0.088,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_can_return_a_list_of_products_with_selected_fields":0.083,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_throws_when_selecting_a_field_that_is_not_allowed":0.17,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_can_make_routes_for_a_product_resource":0.006,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_can_return_a_list_of_products_with_a_custom_transformer":0.081,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_throws_when_filtering_by_a_field_that_is_not_allowed":0.124,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_can_return_a_list_of_products_with_selected_sorts":0.086,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_throws_when_sorting_by_a_field_that_is_not_allowed":0.124,"P\\Tests\\ApiServiceTest::__pest_evaluable_it_can_return_a_list_of_products_with_allowed_attributes":0.082,"P\\Tests\\ArchTest::__pest_evaluable_it_will_not_use_debugging_functions":0.134}} \ No newline at end of file diff --git a/README.md b/README.md index aa72a3b..c3e059e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ php artisan make:filament-api-service BlogResource From version 3.0, routes automatically registered. it will grouped as '/api/`admin`'. `admin` is panelId. - So, You don't need to register the routes manually. The routes will be : @@ -47,7 +46,18 @@ The routes will be : On CreateHandler, you need to be create your custom request validation. -Im using `"spatie/laravel-query-builder": "^5.3"` to handle query selecting, sorting and filtering. Check out [the spatie/laravel-query-builder documentation](https://spatie.be/docs/laravel-query-builder/v5/introduction) for more information. + +## Publish Token Resource + +```bash +php artisan vendor:publish --tag=api-service-resource +``` + +and you can modify it and show for spesific users by your own Authorization system + +## Filtering & Allowed Field + +We used `"spatie/laravel-query-builder": "^5.3"` to handle query selecting, sorting and filtering. Check out [the spatie/laravel-query-builder documentation](https://spatie.be/docs/laravel-query-builder/v5/introduction) for more information. You can specified `allowedFilters` and `allowedFields` in your model. For example: diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index ad58683..6c8c922 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -78,11 +78,9 @@ public function packageBooted(): void // Handle Stubs if (app()->runningInConsole()) { - foreach (app(Filesystem::class)->files(__DIR__.'/../stubs/') as $file) { - $this->publishes([ - $file->getRealPath() => base_path("stubs/api-service/{$file->getFilename()}"), - ], 'api-service-stubs'); - } + $this->publishes([ + __DIR__ . '/Resources' => app_path('/Filament/Resources') + ], 'api-service-resource'); } $router = app('router'); From 323454da54ac96bde8c916e0d43b1e0364797835 Mon Sep 17 00:00:00 2001 From: rupadana Date: Sat, 27 Jan 2024 09:30:44 +0000 Subject: [PATCH 23/31] Fix styling --- src/ApiServiceServiceProvider.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index 6c8c922..41d4ed1 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -5,7 +5,6 @@ use Filament\Support\Assets\Asset; use Filament\Support\Facades\FilamentAsset; use Filament\Support\Facades\FilamentIcon; -use Illuminate\Filesystem\Filesystem; use Laravel\Sanctum\Http\Middleware\CheckAbilities; use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; use Rupadana\ApiService\Commands\MakeApiHandlerCommand; @@ -79,7 +78,7 @@ public function packageBooted(): void // Handle Stubs if (app()->runningInConsole()) { $this->publishes([ - __DIR__ . '/Resources' => app_path('/Filament/Resources') + __DIR__.'/Resources' => app_path('/Filament/Resources'), ], 'api-service-resource'); } From 1020a36574a4593d13ea91dec14489267d9caf52 Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Mon, 29 Jan 2024 10:11:15 +0800 Subject: [PATCH 24/31] update token resource access --- README.md | 8 ++------ config/api-service.php | 5 +++++ src/ApiServiceServiceProvider.php | 7 ------- src/Resources/TokenResource.php | 5 +++++ 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c3e059e..15440c2 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,9 @@ The routes will be : On CreateHandler, you need to be create your custom request validation. -## Publish Token Resource +## Token Resource -```bash -php artisan vendor:publish --tag=api-service-resource -``` - -and you can modify it and show for spesific users by your own Authorization system +By default, Token resource only show on `super_admin` role. you can modify it by publishing config and change `api-service.can_access.role` ## Filtering & Allowed Field diff --git a/config/api-service.php b/config/api-service.php index 9a77c02..18ccbc4 100644 --- a/config/api-service.php +++ b/config/api-service.php @@ -7,4 +7,9 @@ 'token' => 'User', ], ], + 'can_access' => [ + 'role' => [ + 'super_admin' + ] + ] ]; diff --git a/src/ApiServiceServiceProvider.php b/src/ApiServiceServiceProvider.php index 41d4ed1..56e6a36 100644 --- a/src/ApiServiceServiceProvider.php +++ b/src/ApiServiceServiceProvider.php @@ -75,13 +75,6 @@ public function packageBooted(): void // Icon Registration FilamentIcon::register($this->getIcons()); - // Handle Stubs - if (app()->runningInConsole()) { - $this->publishes([ - __DIR__.'/Resources' => app_path('/Filament/Resources'), - ], 'api-service-resource'); - } - $router = app('router'); $router->aliasMiddleware('abilities', CheckAbilities::class); $router->aliasMiddleware('ability', CheckForAnyAbility::class); diff --git a/src/Resources/TokenResource.php b/src/Resources/TokenResource.php index 55fdc76..582056c 100644 --- a/src/Resources/TokenResource.php +++ b/src/Resources/TokenResource.php @@ -118,4 +118,9 @@ public static function getNavigationGroup(): ?string { return config('api-service.navigation.group.token'); } + + public static function canAccess(): bool + { + return auth()->user()->hasRole(config('api-service.can_access.role', [])); + } } From a048c77c22530989d331d67ea5fa1988069214a1 Mon Sep 17 00:00:00 2001 From: rupadana Date: Mon, 29 Jan 2024 02:11:47 +0000 Subject: [PATCH 25/31] Fix styling --- config/api-service.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/api-service.php b/config/api-service.php index 18ccbc4..5578f60 100644 --- a/config/api-service.php +++ b/config/api-service.php @@ -9,7 +9,7 @@ ], 'can_access' => [ 'role' => [ - 'super_admin' - ] - ] + 'super_admin', + ], + ], ]; From d14282a9d443762d4b5bae7ae44a42bb98960745 Mon Sep 17 00:00:00 2001 From: rupadana Date: Mon, 29 Jan 2024 02:12:43 +0000 Subject: [PATCH 26/31] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be0e0be..085d009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `api-service` will be documented in this file. +## 3.0.5 - 2024-01-29 + +**Full Changelog**: https://github.com/rupadana/filament-api-service/compare/3.0.2...3.0.5 + ## 3.0.4 - 2024-01-27 **Full Changelog**: https://github.com/rupadana/filament-api-service/compare/3.0.1...3.0.4 From 39ad6c3d31fd1cc57edaaf460e04cbaad8839208 Mon Sep 17 00:00:00 2001 From: I Wayan Rupadana Date: Mon, 29 Jan 2024 15:20:32 +0800 Subject: [PATCH 27/31] add TokenPolicy & AuthServiceProvider --- composer.json | 148 ++++++++++++++++--------------- src/AuthServiceProvider.php | 21 +++++ src/Policies/TokenPolicy.php | 150 ++++++++++++++++++++++++++++++++ src/Resources/TokenResource.php | 5 -- 4 files changed, 247 insertions(+), 77 deletions(-) create mode 100644 src/AuthServiceProvider.php create mode 100644 src/Policies/TokenPolicy.php diff --git a/composer.json b/composer.json index 711a72c..94cd327 100644 --- a/composer.json +++ b/composer.json @@ -1,73 +1,77 @@ { - "name": "rupadana/filament-api-service", - "description": "A simple api service for supporting filamentphp", - "keywords": [ - "rupadana", - "laravel", - "api-service" - ], - "homepage": "https://github.com/rupadana/api-service", - "support": { - "issues": "https://github.com/rupadana/api-service/issues", - "source": "https://github.com/rupadana/api-service" - }, - "license": "MIT", - "authors": [ - { - "name": "Rupadana", - "email": "rupadanawayan@gmail.com", - "role": "Developer" - } - ], - "require": { - "php": "^8.1", - "laravel/framework": "^10.10", - "laravel/sanctum": "^3.2", - "filament/filament": "^3.0", - "spatie/laravel-package-tools": "^1.14.0", - "illuminate/contracts": "^10.0", - "spatie/laravel-query-builder": "^5.3" - }, - "require-dev": { - "nunomaduro/collision": "^7.9", - "orchestra/testbench": "^8.0", - "pestphp/pest": "^2.0", - "pestphp/pest-plugin-arch": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0", - "phpunit/phpunit": "^10.0.17" - }, - "autoload": { - "psr-4": { - "Rupadana\\ApiService\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Rupadana\\ApiService\\Tests\\": "tests/" - } - }, - "scripts": { - "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", - "test": "vendor/bin/pest", - "test-coverage": "vendor/bin/pest --coverage" - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "pestphp/pest-plugin": true, - "phpstan/extension-installer": true - } - }, - "extra": { - "laravel": { - "providers": [ - "Rupadana\\ApiService\\ApiServiceServiceProvider" - ], - "aliases": { - "ApiService": "Rupadana\\ApiService\\Facades\\ApiService" - } - } - }, - "minimum-stability": "dev", - "prefer-stable": true -} \ No newline at end of file + "name": "rupadana/filament-api-service", + "description": "A simple api service for supporting filamentphp", + "keywords": [ + "rupadana", + "laravel", + "api-service", + "api", + "filament", + "filament api" + ], + "homepage": "https://github.com/rupadana/api-service", + "support": { + "issues": "https://github.com/rupadana/api-service/issues", + "source": "https://github.com/rupadana/api-service" + }, + "license": "MIT", + "authors": [ + { + "name": "Rupadana", + "email": "rupadanawayan@gmail.com", + "role": "Developer" + } + ], + "require": { + "php": "^8.1", + "laravel/framework": "^10.10", + "laravel/sanctum": "^3.2", + "filament/filament": "^3.2", + "spatie/laravel-package-tools": "^1.14.0", + "illuminate/contracts": "^10.0", + "spatie/laravel-query-builder": "^5.3" + }, + "require-dev": { + "nunomaduro/collision": "^7.9", + "orchestra/testbench": "^8.0", + "pestphp/pest": "^2.0", + "pestphp/pest-plugin-arch": "^2.0", + "pestphp/pest-plugin-laravel": "^2.0", + "phpunit/phpunit": "^10.0.17" + }, + "autoload": { + "psr-4": { + "Rupadana\\ApiService\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Rupadana\\ApiService\\Tests\\": "tests/" + } + }, + "scripts": { + "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", + "test": "vendor/bin/pest", + "test-coverage": "vendor/bin/pest --coverage" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true + } + }, + "extra": { + "laravel": { + "providers": [ + "Rupadana\\ApiService\\ApiServiceServiceProvider", + "Rupadana\\ApiService\\AuthServiceProvider" + ], + "aliases": { + "ApiService": "Rupadana\\ApiService\\Facades\\ApiService" + } + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/src/AuthServiceProvider.php b/src/AuthServiceProvider.php new file mode 100644 index 0000000..dd95522 --- /dev/null +++ b/src/AuthServiceProvider.php @@ -0,0 +1,21 @@ + TokenPolicy::class + ]; + + /** + * Register any authentication / authorization services. + */ + public function boot(): void + { + } +} diff --git a/src/Policies/TokenPolicy.php b/src/Policies/TokenPolicy.php new file mode 100644 index 0000000..db1a272 --- /dev/null +++ b/src/Policies/TokenPolicy.php @@ -0,0 +1,150 @@ +can('view_any_token'); + } + + /** + * Determine whether the user can view the model. + * + * @param \App\Models\User $user + * @param \Rupadana\ApiService\Models\Token $token + * @return bool + */ + public function view(User $user, Token $token): bool + { + return $user->can('view_token'); + } + + /** + * Determine whether the user can create models. + * + * @param \App\Models\User $user + * @return bool + */ + public function create(User $user): bool + { + return $user->can('create_token'); + } + + /** + * Determine whether the user can update the model. + * + * @param \App\Models\User $user + * @param \Rupadana\ApiService\Models\Token $token + * @return bool + */ + public function update(User $user, Token $token): bool + { + return $user->can('update_token'); + } + + /** + * Determine whether the user can delete the model. + * + * @param \App\Models\User $user + * @param \Rupadana\ApiService\Models\Token $token + * @return bool + */ + public function delete(User $user, Token $token): bool + { + return $user->can('delete_token'); + } + + /** + * Determine whether the user can bulk delete. + * + * @param \App\Models\User $user + * @return bool + */ + public function deleteAny(User $user): bool + { + return $user->can('delete_any_token'); + } + + /** + * Determine whether the user can permanently delete. + * + * @param \App\Models\User $user + * @param \Rupadana\ApiService\Models\Token $token + * @return bool + */ + public function forceDelete(User $user, Token $token): bool + { + return $user->can('force_delete_token'); + } + + /** + * Determine whether the user can permanently bulk delete. + * + * @param \App\Models\User $user + * @return bool + */ + public function forceDeleteAny(User $user): bool + { + return $user->can('force_delete_any_token'); + } + + /** + * Determine whether the user can restore. + * + * @param \App\Models\User $user + * @param \Rupadana\ApiService\Models\Token $token + * @return bool + */ + public function restore(User $user, Token $token): bool + { + return $user->can('restore_token'); + } + + /** + * Determine whether the user can bulk restore. + * + * @param \App\Models\User $user + * @return bool + */ + public function restoreAny(User $user): bool + { + return $user->can('restore_any_token'); + } + + /** + * Determine whether the user can replicate. + * + * @param \App\Models\User $user + * @param \Rupadana\ApiService\Models\Token $token + * @return bool + */ + public function replicate(User $user, Token $token): bool + { + return $user->can('replicate_token'); + } + + /** + * Determine whether the user can reorder. + * + * @param \App\Models\User $user + * @return bool + */ + public function reorder(User $user): bool + { + return $user->can('reorder_token'); + } +} diff --git a/src/Resources/TokenResource.php b/src/Resources/TokenResource.php index 582056c..55fdc76 100644 --- a/src/Resources/TokenResource.php +++ b/src/Resources/TokenResource.php @@ -118,9 +118,4 @@ public static function getNavigationGroup(): ?string { return config('api-service.navigation.group.token'); } - - public static function canAccess(): bool - { - return auth()->user()->hasRole(config('api-service.can_access.role', [])); - } } From 787cc9fd4dad0315008dead021f98ac6e7d859d1 Mon Sep 17 00:00:00 2001 From: rupadana Date: Mon, 29 Jan 2024 07:20:56 +0000 Subject: [PATCH 28/31] Fix styling --- src/AuthServiceProvider.php | 2 +- src/Policies/TokenPolicy.php | 44 +----------------------------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/src/AuthServiceProvider.php b/src/AuthServiceProvider.php index dd95522..3ba5a5a 100644 --- a/src/AuthServiceProvider.php +++ b/src/AuthServiceProvider.php @@ -9,7 +9,7 @@ class AuthServiceProvider extends ServiceProvider { protected $policies = [ - Token::class => TokenPolicy::class + Token::class => TokenPolicy::class, ]; /** diff --git a/src/Policies/TokenPolicy.php b/src/Policies/TokenPolicy.php index db1a272..001bbe1 100644 --- a/src/Policies/TokenPolicy.php +++ b/src/Policies/TokenPolicy.php @@ -3,8 +3,8 @@ namespace Rupadana\ApiService\Policies; use App\Models\User; -use Rupadana\ApiService\Models\Token; use Illuminate\Auth\Access\HandlesAuthorization; +use Rupadana\ApiService\Models\Token; class TokenPolicy { @@ -12,9 +12,6 @@ class TokenPolicy /** * Determine whether the user can view any models. - * - * @param \App\Models\User $user - * @return bool */ public function viewAny(User $user): bool { @@ -23,10 +20,6 @@ public function viewAny(User $user): bool /** * Determine whether the user can view the model. - * - * @param \App\Models\User $user - * @param \Rupadana\ApiService\Models\Token $token - * @return bool */ public function view(User $user, Token $token): bool { @@ -35,9 +28,6 @@ public function view(User $user, Token $token): bool /** * Determine whether the user can create models. - * - * @param \App\Models\User $user - * @return bool */ public function create(User $user): bool { @@ -46,10 +36,6 @@ public function create(User $user): bool /** * Determine whether the user can update the model. - * - * @param \App\Models\User $user - * @param \Rupadana\ApiService\Models\Token $token - * @return bool */ public function update(User $user, Token $token): bool { @@ -58,10 +44,6 @@ public function update(User $user, Token $token): bool /** * Determine whether the user can delete the model. - * - * @param \App\Models\User $user - * @param \Rupadana\ApiService\Models\Token $token - * @return bool */ public function delete(User $user, Token $token): bool { @@ -70,9 +52,6 @@ public function delete(User $user, Token $token): bool /** * Determine whether the user can bulk delete. - * - * @param \App\Models\User $user - * @return bool */ public function deleteAny(User $user): bool { @@ -81,10 +60,6 @@ public function deleteAny(User $user): bool /** * Determine whether the user can permanently delete. - * - * @param \App\Models\User $user - * @param \Rupadana\ApiService\Models\Token $token - * @return bool */ public function forceDelete(User $user, Token $token): bool { @@ -93,9 +68,6 @@ public function forceDelete(User $user, Token $token): bool /** * Determine whether the user can permanently bulk delete. - * - * @param \App\Models\User $user - * @return bool */ public function forceDeleteAny(User $user): bool { @@ -104,10 +76,6 @@ public function forceDeleteAny(User $user): bool /** * Determine whether the user can restore. - * - * @param \App\Models\User $user - * @param \Rupadana\ApiService\Models\Token $token - * @return bool */ public function restore(User $user, Token $token): bool { @@ -116,9 +84,6 @@ public function restore(User $user, Token $token): bool /** * Determine whether the user can bulk restore. - * - * @param \App\Models\User $user - * @return bool */ public function restoreAny(User $user): bool { @@ -127,10 +92,6 @@ public function restoreAny(User $user): bool /** * Determine whether the user can replicate. - * - * @param \App\Models\User $user - * @param \Rupadana\ApiService\Models\Token $token - * @return bool */ public function replicate(User $user, Token $token): bool { @@ -139,9 +100,6 @@ public function replicate(User $user, Token $token): bool /** * Determine whether the user can reorder. - * - * @param \App\Models\User $user - * @return bool */ public function reorder(User $user): bool { From 71d754e77d32136d5904de9d63c2df8b6188ed4d Mon Sep 17 00:00:00 2001 From: rupadana Date: Mon, 29 Jan 2024 07:21:39 +0000 Subject: [PATCH 29/31] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 085d009..2e5232d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `api-service` will be documented in this file. +## 3.0.6 - 2024-01-29 + +**Full Changelog**: https://github.com/rupadana/filament-api-service/compare/3.0.5...3.0.6 + ## 3.0.5 - 2024-01-29 **Full Changelog**: https://github.com/rupadana/filament-api-service/compare/3.0.2...3.0.5 From 5d27b66d37058c57a5daa006549e400e30aae483 Mon Sep 17 00:00:00 2001 From: Rupadana <34137674+rupadana@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:54:07 +0800 Subject: [PATCH 30/31] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15440c2..9fa6d74 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ ![Run Test](https://github.com/rupadana/filament-api-service/actions/workflows/run-tests.yml/badge.svg?branch=main) - ## Installation You can install the package via composer: @@ -187,3 +186,6 @@ Please review [our security policy](../../security/policy) on how to report secu ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. + +## Supported By + From b2a0517e3c1a688e61b508c5a2f096c132be79d7 Mon Sep 17 00:00:00 2001 From: Rupadana <34137674+rupadana@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:20:05 +0800 Subject: [PATCH 31/31] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9fa6d74..78d76e7 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,10 @@ To Generate Token, you just need create it from admin panel. It will be Token Re ![Image](https://res.cloudinary.com/rupadana/image/upload/v1704958748/Screenshot_2024-01-11_at_15.37.55_ncpg8n.png) +## TODO + +- [ ] Test Plugin for Tenancy purpose + ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.