Skip to content

Commit

Permalink
Merge pull request #82 from rupadana/v3
Browse files Browse the repository at this point in the history
adding scramble for documentation and built-in authentication
  • Loading branch information
rupadana authored Jan 18, 2025
2 parents 8fbceec + b2292c5 commit c45e607
Show file tree
Hide file tree
Showing 32 changed files with 946 additions and 80 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,16 @@ Overriding tenancy ownership relationship name by adding this property to the Ha

### How to secure it?

Since version 3.0, it will automatically detect routes and secure it using sanctum.
Since version 3.4, this plugin includes built-in authentication routes:

To Generate Token, you just need create it from admin panel. It will be Token Resource there.
| Method | Endpoint | Description |
| ------ | -------------------- | --------------------------- |
| POST | /api/auth/login | Login |
| POST | /api/auth/logout | Logout |

We also use the permission middleware from [spatie/laravel-permission](https://spatie.be/docs/laravel-permission/v6/basic-usage/middleware). making it easier to integrate with [filament-shield](https://github.com/bezhanSalleh/filament-shield)

![Image](https://res.cloudinary.com/rupadana/image/upload/v1704958748/Screenshot_2024-01-11_at_15.37.55_ncpg8n.png)
If you prefer to use the old version of the middleware, please set 'use-spatie-permission-middleware' => false.

### Public API

Expand Down
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
],
"require": {
"php": "^8.1",
"dedoc/scramble": "^0.11.12",
"filament/filament": "^3.2",
"illuminate/contracts": "^10.0|^11.0",
"laravel/framework": "^10.10|^11.0",
"laravel/sanctum": "^3.2|^4.0",
"filament/filament": "^3.2",
"spatie/laravel-package-tools": "^1.14.0",
"illuminate/contracts": "^10.0|^11.0",
"spatie/laravel-query-builder": "^5.3|^6.2"
"spatie/laravel-query-builder": "^5.3|^6.2",
"spatie/laravel-permission": "^6.0"
},
"require-dev": {
"nunomaduro/collision": "^7.9|^8.0",
Expand Down
5 changes: 5 additions & 0 deletions config/api-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@
'enabled' => false,
'awareness' => false,
],
'login-rules' => [
'email' => 'required|email',
'password' => 'required',
],
'use-spatie-permission-middleware' => true,
];
7 changes: 6 additions & 1 deletion routes/api.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
<?php

use Filament\Facades\Filament;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
use Rupadana\ApiService\ApiService;
use Rupadana\ApiService\Exceptions\InvalidTenancyConfiguration;
use Rupadana\ApiService\Http\Controllers\AuthController;

Route::prefix('api')
->name('api.')
->group(function () {
->group(function (Router $router) {
$router->post('/auth/login', [AuthController::class, 'login']);
$router->post('/auth/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');

if (ApiService::tenancyAwareness() && (! ApiService::isRoutePrefixedByPanel() || ! ApiService::isTenancyEnabled())) {
throw new InvalidTenancyConfiguration("Tenancy awareness is enabled!. Please set 'api-service.route.panel_prefix=true' and 'api-service.tenancy.enabled=true'");
}
Expand Down
4 changes: 1 addition & 3 deletions src/ApiServicePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ public function register(Panel $panel): void
]);
}

public function boot(Panel $panel): void
{
}
public function boot(Panel $panel): void {}

public static function getAbilities(Panel $panel): array
{
Expand Down
22 changes: 19 additions & 3 deletions src/ApiServiceServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@

namespace Rupadana\ApiService;

use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Dedoc\Scramble\Support\Generator\SecurityScheme;
use Filament\Support\Assets\Asset;
use Filament\Support\Facades\FilamentAsset;
use Filament\Support\Facades\FilamentIcon;
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
use Rupadana\ApiService\Commands\MakeApiHandlerCommand;
use Rupadana\ApiService\Commands\MakeApiRequest;
use Rupadana\ApiService\Commands\MakeApiServiceCommand;
use Rupadana\ApiService\Commands\MakeApiTransformerCommand;
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Spatie\Permission\Middleware\PermissionMiddleware;
use Spatie\Permission\Middleware\RoleMiddleware;
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;

class ApiServiceServiceProvider extends PackageServiceProvider
{
Expand Down Expand Up @@ -54,9 +61,7 @@ public function configurePackage(Package $package): void
}
}

public function packageRegistered(): void
{
}
public function packageRegistered(): void {}

public function packageBooted(): void
{
Expand All @@ -77,6 +82,16 @@ public function packageBooted(): void
$router = app('router');
$router->aliasMiddleware('abilities', CheckAbilities::class);
$router->aliasMiddleware('ability', CheckForAnyAbility::class);
$router->aliasMiddleware('role', RoleMiddleware::class);
$router->aliasMiddleware('permission', PermissionMiddleware::class);
$router->aliasMiddleware('role_or_permission', RoleOrPermissionMiddleware::class);

// Configure Scramble Authentication
Scramble::afterOpenApiGenerated(function (OpenApi $openApi) {
$openApi->secure(
SecurityScheme::http('bearer')
);
});
}

protected function getAssetPackageName(): ?string
Expand All @@ -101,6 +116,7 @@ protected function getCommands(): array
MakeApiHandlerCommand::class,
MakeApiServiceCommand::class,
MakeApiTransformerCommand::class,
MakeApiRequest::class,
];
}

Expand Down
4 changes: 1 addition & 3 deletions src/AuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ class AuthServiceProvider extends ServiceProvider
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
}
public function boot(): void {}
}
152 changes: 152 additions & 0 deletions src/Commands/MakeApiRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace Rupadana\ApiService\Commands;

use Filament\Facades\Filament;
use Filament\Panel;
use Filament\Support\Commands\Concerns\CanManipulateFiles;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;

use function Laravel\Prompts\select;
use function Laravel\Prompts\text;

class MakeApiRequest extends Command
{
use CanManipulateFiles;
protected $description = 'Create a new API Request';
protected $signature = 'make:filament-api-request {name?} {resource?} {--panel=}';

public function handle(): int
{
$name = (string) str($this->argument('name') ?? text(
label: 'What is the Request name?',
placeholder: 'CreateRequest',
required: true,
))
->studly()
->beforeLast('Request')
->trim('/')
->trim('\\')
->trim(' ')
->studly()
->replace('/', '\\');

$model = (string) str($this->argument('resource') ?? text(
label: 'What is the Resource name?',
placeholder: 'Blog',
required: true,
))
->studly()
->beforeLast('Resource')
->trim('/')
->trim('\\')
->trim(' ')
->studly()
->replace('/', '\\');

if (blank($model)) {
$model = 'Resource';
}

$modelClass = (string) str($model)->afterLast('\\');

$modelNamespace = str($model)->contains('\\') ?
(string) str($model)->beforeLast('\\') :
'';
$pluralModelClass = (string) str($modelClass)->pluralStudly();

$panel = $this->option('panel');

if ($panel) {
$panel = Filament::getPanel($panel);
}

if (! $panel) {
$panels = Filament::getPanels();

/** @var Panel $panel */
$panel = (count($panels) > 1) ? $panels[select(
label: 'Which panel would you like to create this in?',
options: array_map(
fn (Panel $panel): string => $panel->getId(),
$panels,
),
default: Filament::getDefaultPanel()->getId()
)] : Arr::first($panels);
}

$resourceDirectories = $panel->getResourceDirectories();
$resourceNamespaces = $panel->getResourceNamespaces();

$namespace = (count($resourceNamespaces) > 1) ?
select(
label: 'Which namespace would you like to create this in?',
options: $resourceNamespaces
) : (Arr::first($resourceNamespaces) ?? 'App\\Filament\\Resources');
$path = (count($resourceDirectories) > 1) ?
$resourceDirectories[array_search($namespace, $resourceNamespaces)] : (Arr::first($resourceDirectories) ?? app_path('Filament/Resources/'));

$nameClass = "{$name}{$model}Request";
$resource = "{$model}Resource";
$resourceClass = "{$modelClass}Resource";
$resourceNamespace = $modelNamespace;
$namespace .= $resourceNamespace !== '' ? "\\{$resourceNamespace}" : '';

$baseResourcePath =
(string) str($resource)
->prepend('/')
->prepend($path)
->replace('\\', '/')
->replace('//', '/');

$requestDirectory = "{$baseResourcePath}/Api/Requests/$nameClass.php";

$modelNamespace = app("{$namespace}\\{$resourceClass}")->getModel();

$this->copyStubToApp('Request', $requestDirectory, [
'namespace' => "{$namespace}\\{$resourceClass}\\Api\\Requests",
'nameClass' => $nameClass,
'validationRules' => $this->getValidationRules(new $modelNamespace),
]);

$this->components->info("Successfully created API {$nameClass} for {$resource}!");

return static::SUCCESS;
}

public function getValidationRules(Model $model)
{
$tableName = $model->getTable();

$columns = DB::select("SHOW COLUMNS FROM `{$tableName}`");

$validationRules = collect($columns)
->filter(function ($column) {
// Mengabaikan kolom 'created_at' dan 'updated_at'
return ! in_array($column->Field, ['id', 'created_at', 'updated_at']);
})
->map(function ($column) {
$field = $column->Field;
$type = $column->Type;

// Pemetaan tipe data ke validasi Laravel menggunakan match
$rule = 'required'; // Tambahkan aturan 'required' sebagai default

$rule .= match (true) {
str_contains($type, 'int') => '|integer',
str_contains($type, 'varchar'), str_contains($type, 'text') => '|string',
str_contains($type, 'date') => '|date',
str_contains($type, 'decimal'), str_contains($type, 'float'), str_contains($type, 'double') => '|numeric',
default => '',
};

return "\t\t\t'{$field}' => '{$rule}'";
})
->implode(",\n");

return "[\n" . $validationRules . "\n\t\t]";
}
}
26 changes: 26 additions & 0 deletions src/Commands/MakeApiServiceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Filament\Support\Commands\Concerns\CanManipulateFiles;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Artisan;

use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
Expand Down Expand Up @@ -77,6 +78,7 @@ public function handle(): int
$resource = "{$model}Resource";
$resourceClass = "{$modelClass}Resource";
$apiServiceClass = "{$model}ApiService";
$transformer = "{$model}Transformer";
$resourceNamespace = $modelNamespace;
$namespace .= $resourceNamespace !== '' ? "\\{$resourceNamespace}" : '';

Expand All @@ -93,6 +95,7 @@ public function handle(): int
->replace('\\', '/')
->replace('//', '/');

$transformerClass = "{$namespace}\\{$resourceClass}\\Api\\Transformers\\{$transformer}";
$handlersNamespace = "{$namespace}\\{$resourceClass}\\Api\\Handlers";

$resourceApiDirectory = "{$baseResourcePath}/Api/$apiServiceClass.php";
Expand All @@ -102,6 +105,19 @@ public function handle(): int
$paginationHandlerDirectory = "{$baseResourcePath}/Api/Handlers/$paginationHandlerClass.php";
$deleteHandlerDirectory = "{$baseResourcePath}/Api/Handlers/$deleteHandlerClass.php";

Artisan::call('make:filament-api-transformer', [
'resource' => $model,
'--panel' => $panel->getId(),
]);
collect(['Create', 'Update'])
->each(function ($name) use ($model, $panel) {
Artisan::call('make:filament-api-request', [
'name' => $name,
'resource' => $model,
'--panel' => $panel->getId(),
]);
});

$this->copyStubToApp('ResourceApiService', $resourceApiDirectory, [
'namespace' => "{$namespace}\\{$resourceClass}\\Api",
'resource' => "{$namespace}\\{$resourceClass}",
Expand All @@ -114,30 +130,40 @@ public function handle(): int
'resource' => "{$namespace}\\{$resourceClass}",
'resourceClass' => $resourceClass,
'handlersNamespace' => $handlersNamespace,
'model' => $model,
]);

$this->copyStubToApp('DetailHandler', $detailHandlerDirectory, [
'resource' => "{$namespace}\\{$resourceClass}",
'resourceClass' => $resourceClass,
'handlersNamespace' => $handlersNamespace,
'transformer' => $transformer,
'transformerClass' => $transformerClass,
'model' => $model,
]);

$this->copyStubToApp('CreateHandler', $createHandlerDirectory, [
'resource' => "{$namespace}\\{$resourceClass}",
'resourceClass' => $resourceClass,
'handlersNamespace' => $handlersNamespace,
'model' => $model,
]);

$this->copyStubToApp('UpdateHandler', $updateHandlerDirectory, [
'resource' => "{$namespace}\\{$resourceClass}",
'resourceClass' => $resourceClass,
'handlersNamespace' => $handlersNamespace,
'model' => $model,
]);

$this->copyStubToApp('PaginationHandler', $paginationHandlerDirectory, [
'resource' => "{$namespace}\\{$resourceClass}",
'resourceClass' => $resourceClass,
'handlersNamespace' => $handlersNamespace,
'transformer' => $transformer,
'transformerClass' => $transformerClass,
'model' => $model,

]);

$this->components->info("Successfully created API for {$resource}!");
Expand Down
Loading

0 comments on commit c45e607

Please sign in to comment.