rupadana / filament-api-service Goto Github PK
View Code? Open in Web Editor NEWA simple api service for supporting filamentphp
Home Page: https://filamentphp.com/plugins/rupadana-api-service
License: MIT License
A simple api service for supporting filamentphp
Home Page: https://filamentphp.com/plugins/rupadana-api-service
License: MIT License
How can I publish the config file api-service.php?
when you try to publish with php artisan vendor:publish --provider="Rupadna\ApiService\ApiServiceServi
ceProvider" --tag=config
it returns No publishable resources for tag [config].
2.0.0
8.1.0
10.10
No response
No response
Hi, appreaciate your plugins, its helping me in a lot of ways, but I have a question.
Currently from the web perspective, I need to restrict some user from accessing the token page resources, how do I hide the token navigation?
Login using any user
3.0
8.1
10.10
Windows
No response
in ApiService.php in function registerRoutes(Panel $panel) there is a static function call to static::handlers(). But this makes the group() of routes in some specific cases incorrect
the array length of the groupStack should be 3 like so:
#groupStack: array:3 [▼
0 => array:2 [▼
"prefix" => "api"
"as" => "api."
]
1 => array:5 [▼
"as" => "api.admin."
"middleware" => array:3 [ …3]
"prefix" => "api/admin/{tenant}"
"namespace" => null
"where" => []
]
2 => array:5 [▼
"middleware" => array:3 [ …3]
"as" => "api.admin.users."
"prefix" => "api/admin/{tenant}/users"
"namespace" => null
"where" => []
]
]
but in somecases a 4th item will get appended with the next resource
like so:
#groupStack: array:3 [▼
0 => array:2 [▼
"prefix" => "api"
"as" => "api."
]
1 => array:5 [▼
"as" => "api.admin."
"middleware" => array:3 [ …3]
"prefix" => "api/admin/{tenant}"
"namespace" => null
"where" => []
]
2 => array:5 [▼
"middleware" => array:3 [ …3]
"as" => "api.admin.users."
"prefix" => "api/admin/{tenant}/users"
"namespace" => null
"where" => []
]
3 => array:5 [▼
"middleware" => array:3 [ …3]
"as" => "api.admin.users."
"prefix" => "api/admin/{tenant}/users/companies"
"namespace" => null
"where" => []
]
]
which is incorrect. because in this example the prefix should be on index 2: api/admin/{tenant}/companies
and not a third index added with: api/admin/{tenant}/users/companies
.
The fix should be:
remove: static::handlers();
in the ->group() function:
$route = Route::name(
$name
)
->middleware($resourceRouteMiddlewares)
->prefix(static::$groupRouteName ?? $slug)
->group(function (Router $route) {
foreach (static::handlers() as $key => $handler) {
app($handler)->route($route);
}
});
see above
3.2
8.3
10
No response
No response
Errors regarding token creation in multi-tenancy mode
Hello! I have been unable to cope with numerous errors related to the creation of the token for several days now. Namely, if I try to go to the page with the creation of tokens, I get an error :
The model [Rupadana\ApiService\Models\Token] does not have a relationship named [company]. You can change the relationship being used by passing it to the [ownershipRelationship] argument of the [tenant()] method in configuration. You can change the relationship being used per-resource by setting it as the [$tenantOwnershipRelationshipName] static property on the [Rupadana\ApiService\Resources\TokenResource] resource class.
But I fixed this error by changing the model Token.php and by changing the migration of create_personal_access_tokens . But now I'm facing a mistake. :
SQLSTATE[HY000]: General error: 1364 Field 'tokenable_type' doesn't have a default value INSERT INTO
personal_access_tokens(
name,
token,
abilities,
expires_at,
user_id,
updated_at,
created_at ) VALUES ( ssaddsa, 9a9284467652ea6ea8e33584f75de3bbfe764c4582120f0dc7b57b9b346c6bde, [ "company-contact:create", "company-contact:update", "company-contact:delete", "company-contact:pagination", "company-contact:detail" ], ?, 1, 2024 -01 -21 21: 31: 00, 2024 -01 -21 21: 31: 00 )
The redesigned version Token.php:
namespace Rupadana\ApiService\Models;
use...
class Token extends PersonalAccessToken
{
use HasFactory;
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);
}
protected $table = 'personal_access_tokens';
}
api-service.php:
// config for Rupadana/ApiService
return [
'navigation' => [
'group' => [
'token' => 'User',
],
],
];
Company:php:
namespace App\Models;
use ...
class Company extends Model
{
protected $fillable = [
'user_id',
'name',
'short_name',
'type',
];
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'
];
public function token(): HasMany
{
return $this->hasMany(Token::class);
}
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
//other functions ...
User.php:
<?php
namespace App\Models;
use ...
class User extends Authenticatable implements HasTenants
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
public function getTenants(Panel $panel): Collection
{
return $this->companies;
}
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class);
}
public function canAccessTenant(Model $tenant): bool
{
return $this->companies->contains($tenant);
}
public function tokens(): HasMany
{
return $this->hasMany(Token::class);
}
}
3.0.2
8.1
10.40
Windows
Friends, your help is very important. I tried to contact the official filament discord, but to no avail. I really hope for you.
Hi,
Is it possible/planned to allow individual users to generate API keys which allows them to access their records/data?
Thanks.
N/A
00
00
00
No response
No response
➜ my-project git:(main) ✗ composer require rupadana/filament-api-service
./composer.json has been updated
Running composer update rupadana/filament-api-service
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- rupadana/filament-api-service[v1.0.0, ..., v1.0.3] require illuminate/contracts ^10.0 -> found illuminate/contracts[v10.0.0, ..., v10.48.3] but these were not loaded, likely because it conflicts with another require.
- rupadana/filament-api-service[3.0.0, ..., 3.1.4] require laravel/framework ^10.10 -> found laravel/framework[v10.10.0, ..., v10.48.3] but it conflicts with your root composer.json require (^11.0).
- Root composer.json requires rupadana/filament-api-service * -> satisfiable by rupadana/filament-api-service[v1.0.0, v1.0.1, v1.0.2, v1.0.3, 3.0.0, ..., 3.1.4].
Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
You can also try re-running composer require with an explicit version constraint, e.g. "composer require rupadana/filament-api-service:*" to figure out if any version is installable, or "composer require rupadana/filament-api-service:^2.1" if you know which you need.
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
Laravel 11 is not supported
Install the package on Laravel 11 app
3.1.4
8.3.1
11.x
No response
No response
Ability to use API versions in URL path or query (?version=v1)
looks like an extension to multiple transformers...
latest
8.3.x
11
No response
No response
Use PHP Swagger API docs generator with https://github.com/DarkaOnLine/L5-Swagger and https://github.com/zircote/swagger-php to add these codeblocks to the stubs.
use OpenApi\Attributes as OA;
#[OA\Info(
title:"API Documentation",
version: "0.1",
contact: new OA\Contact(name: "", email: "")
)]
#[OA\Server(
description: "API Server",
url: "http://localhost:8000/api/"
)]
#[OA\SecurityScheme(
name: "bearerAuth",
securityScheme: "bearerAuth",
type: "http",
scheme: "bearer",
description: "Enter JWT Token",
in: "header"
)]
#[OA\PathParameter(
name: 'tenant',
parameter: 'tenant',
description: 'ID of the tenant',
schema: new OA\Schema(type: 'integer'),
required: TENANT_AWARENESS,
in: "path",
)]
Most of the parameters can be set as custom CONSTANTS when the plugin is configured via config variables in l5-swagger.php
define('TENANT_AWARENESS', config('api-service.tenancy.awareness');
or so...
check if tenancy for resource is enabled set constant per resource in for example the BrandApiService.php
#[OA\Tag(
name: "Brands",
description: "API Endpoints of Brands",
)]
class BrandApiService extends ApiService
{
public static function handlers(): array
{
if (
ApiService::isTenancyEnabled() &&
ApiService::tenancyAwareness() &&
static::getResource()::isScopedToTenant()
) {
define('TENANT_AWARENESS_BRANDS', true);
} else {
define('TENANT_AWARENESS_BRANDS', false);
}
$handlers = [
Handlers\CreateHandler::class,
Handlers\UpdateHandler::class,
Handlers\DeleteHandler::class,
Handlers\PaginationHandler::class,
Handlers\DetailHandler::class
];
foreach($handlers as $handler) {
if(app($handler)->isPublic()) {
define('RESOURCE_PUBLIC_BRANDS_' . Str::upper(app($handler)->getKebabClassName()), true);
} else {
define('RESOURCE_PUBLIC_BRANDS_' . Str::upper(app($handler)->getKebabClassName()), false);
}
}
return $handlers;
}
}
as an example for the different resource API Handlers.php (CreateHandler.php, UpdateHandler.php etc...) :
use OpenApi\Attributes as OA;
#[OA\Post(
path: "/admin/" . ((TENANT_AWARENESS_BRANDS) ? "{tenant}/" : "") . "brands",
operationId: "storeBrand",
tags: ["Brands"],
summary: "Store new Brand",
description: "Returns inserted brand data",
security: (!RESOURCE_PUBLIC_BRANDS_CREATE) ? [["bearerAuth" => []]]: null,
parameters: [
(TENANT_AWARENESS_BRANDS) ? new OA\Parameter(ref: "#/components/parameters/tenant") : null,
],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(ref: "#/components/schemas/BrandTransformer/properties/data/items")
),
responses: [
new OA\Response(response: 200, description: 'Operation succesful', content: new OA\JsonContent(type: "object", properties: [
new OA\Property(property: "message", type: "string", example: "Successfully Inserted Resource"),
new OA\Property(property: "data", type: "object", ref: "#/components/schemas/BrandTransformer/properties/data/items")
])),
new OA\Response(response: 400, description: 'Bad Request'),
new OA\Response(response: 401, description: 'Unauthenticated'),
new OA\Response(response: 403, description: 'Forbidden'),
new OA\Response(response: 404, description: 'Resource not Found'),
]
)]
and
#[OA\Get(
path: "/admin/" . ((TENANT_AWARENESS_BRANDS) ? "{tenant}/" : "") . "brands/{id}",
operationId: "getBrandDetail",
tags: ["Brands"],
summary: "Get detail of Brand",
description: "Returns detail of Brand",
security: (!RESOURCE_PUBLIC_BRANDS_DETAIL) ? [["bearerAuth" => []]]: null,
parameters: [
(TENANT_AWARENESS_BRANDS) ? new OA\Parameter(ref: "#/components/parameters/tenant") : null,
new OA\Parameter(
name: "id",
description: "Brand ID",
required: true,
in: "path",
schema: new OA\Schema(type: "integer"),
example: "", // OA\Examples(example="int", value="0", summary="An int value."),
),
new OA\Parameter(
name: "page[offset]",
description: "Pagination offset option",
required: false,
in: "query",
schema: new OA\Schema(type: "integer"),
example: "", // OA\Examples(example="int", value="0", summary="An int value."),
),
new OA\Parameter(
name: "page[limit]",
description: "Pagination limit option",
required: false,
in: "query",
schema: new OA\Schema(type: "integer"),
example: "", // OA\Examples(example="int", value="0", summary="An int value."),
),
new OA\Parameter(
name: "sort",
description: "Sorting",
required: false,
in: "query",
schema: new OA\Schema(type: "string"),
example: "", // @OA\Examples(example="string", value="-created,name", summary="A comma separated value"),
),
new OA\Parameter(
name: "include",
description: "Include Relationships",
required: false,
in: "query",
schema: new OA\Schema(type: "string"),
example: "", // @OA\Examples(example="string", value="order,user", summary="A comma separated value of relationships"),
),
],
responses: [
new OA\Response(response: 200, description: 'Operation succesful', content: new OA\JsonContent( type: "object", ref: "#/components/schemas/BrandTransformer/properties/data/items")),
new OA\Response(response: 400, description: 'Bad Request'),
new OA\Response(response: 401, description: 'Unauthenticated'),
new OA\Response(response: 403, description: 'Forbidden'),
new OA\Response(response: 404, description: 'Resource not Found'),
]
)]
and BrandTransformer.php
#[OA\Schema(
title: "BrandTransformer",
description: "Brands API Transformer",
xml: new OA\XML(name: "BrandTransformer"),
)]
and the transformer schema
#[OA\Property(
property: "data",
type: "array",
items: new OA\Items(
properties: [
new OA\Property(property: "id", type: "integer", title: "ID", description: "id of the brand", example: ""),
// add your own...
new OA\Property(property: "created_at", type: "string", title: "Created at", description: "Created at Datetime of Product", example: ""),
new OA\Property(property: "updated_at", type: "string", title: "Updated at", description: "Updated at Datetime of Product", example: ""),
]
),
)]
#[OA\Property(
property: "meta",
type: "object",
properties: [
new OA\Property(property: "current_page", type: "integer", title: "Current Page", description: "current page of brand", example: ""),
new OA\Property(property: "from", type: "integer", title: "", description: "", example: ""),
new OA\Property(property: "last_page", type: "integer", title: "", description: "", example: ""),
new OA\Property(property: "path", type: "string", title: "", description: "", example: ""),
new OA\Property(property: "per_page", type: "integer", title: "", description: "", example: ""),
new OA\Property(property: "to", type: "integer", title: "", description: "", example: ""),
new OA\Property(property: "total", type: "integer", title: "", description: "", example: ""),
new OA\Property(property: "", type: "integer", title: "", description: "", example: ""),
new OA\Property(
property: "links",
type: "array",
items: new OA\Items(properties: [
new OA\Property(property: "url", type: "string", description: "URL of Pagination"),
new OA\Property(property: "label", type: "string", description: "Label of Pagination"),
new OA\Property(property: "active", type: "bool", description: "Pagination is Active"),
])
)
]
)]
#[OA\Property(
property: "links",
type: "object",
properties: [
new OA\Property(property: "first", type: "string", description: "first page link URL of brand", example: ""),
new OA\Property(property: "last", type: "string", description: "last page link URL of brand", example: ""),
new OA\Property(property: "prev", type: "string", description: "prev page link URL of brand", example: ""),
new OA\Property(property: "next", type: "string", description: "next page link URL of brand", example: ""),
])
]
Implement OpenApi\Attributes;
3.2
8.3
10.0
No response
This is just a start and code is not always cleaned up and i'm not quite sure everything is covered like response codes, panel prefix as CONST so it can be added in path: etc.... I could make time to add these features and do a PR if you prefer.
localhost:8000/api/products
i have create Product api service using following command
php artisan make:filament-api-service ProductResource
its showing routes on artisan route:list but when try to access using postman getting error
config fie
3.2.4
8.3
10.10
Windows
No response
I've a few resources with parent/child relationships, where the child relations resource do not appear on top level, and are accessed via getRelations
method in the parent resource.
How can I create the API for these children within the context of the parent, such as:
PUT /api/parents/{id}/children/{id}
N/A
3.2
8.2.18
11.9.2
macOS
No response
I get the following error message : Route [login] not defined.
When I access to a route: e.g: api/admin/customers/1 I get an error message which implies the route login not defined.
3.0
8.2.12
10.35.0
Windows
No response
missing allowedIncludes
could you add allowedIncludes to the PaginationHandler.stub like so:
$query = QueryBuilder::for($model)
->allowedFields($model::$allowedFields ?? [])
->allowedSorts($model::$allowedSorts ?? [])
->allowedFilters($model::$allowedFilters ?? [])
->allowedIncludes($model::$allowedIncludes ?? null)
->paginate(request()->query('per_page'))
->appends(request()->query());
then it's at least added to the stub as reference..
.
latest
8.3
10
No response
No response
in HasHandlerTenantScope.php it's better to use getRouteKeyName() instead of ->getKeyName()
because if you want for security reasons want the "tenant" to have an UUID instead of an ID you can override the getRouteKeyName() in the Tenant Model, that way it will be used in all URLs and as the tenantID in filament is also an url attribute http://somedomain.com/admin/{tenant}/
it would be better to use the getRouteKeyName().
in Illuminate\Database\Eloquent\Model getRouteKeyName() returns also $this->getKeyName();
so it should fallback nicely.
$tenant = $tenantModel::where(Filament::getCurrentPanel()->getTenantSlugAttribute() ?? $tenantModel->getRouteKeyName(), $tenantId)->first()
uuid as route
latest
8.3
10
No response
No response
Allow handlers to conform to DTO. (these also need to be created/generated...)
$query = static::getEloquentQuery();
$model = static::getModel();
$query = QueryBuilder::for($query)
->allowedFields($model::$allowedFields ?? [])
->allowedSorts($model::$allowedSorts ?? [])
->allowedFilters($model::$allowedFilters ?? [])
->allowedIncludes($model::$allowedIncludes ?? null)
->paginate(request()->query('per_page'))
->appends(request()->query())
;
instead of:
return static::getApiTransformer()::collection($query)
use this:
return static::getApiTransformer()::collection(BlogData::collect($query));
where BlogData is a spatie laravel-data DTO.
The benefit would be that because the DTO knows column types etc. this information can be used to generate correct API Swagger docs for Schemas, Parameters etc.. see:
https://github.com/xolvionl/laravel-data-openapi-generator/tree/main/src/Data
nice to have
latest
8.3
11
No response
No response
possibility to define multiple Transformers (in Resource) as array. And use a specific transformer via HTTP headers defined in the config:
small example code:
api-service.php
'api_transformer_header' => env('API_TRANSFORMER_HEADER', 'X-API-TRANSFORMER'),
$headerName = config('api-service.route.api_transformer_header');
$headerName = strtolower($headerName);
if (!request()->headers->has($headerName)) {
return DefaultTransformer::class;
}
$transformer = request()->headers->get($headerName);
etc.. etc...
use $transformer as key of the resource apiTransformer Array defined list and return the corresponding transformer class,
new addition
latest
8.3
11
No response
No response
Allow for an easy "allow all" Ability via ["*"] on the Token Resource page.
none
3
8.3
10
No response
No response
Call to undefined method App\Models\User::createToken()
Try to create the Token but it seems this function is not existed in User model. Do you have to entend any traits?
When creating token from Token resource
3.2
8.2
11
Linux
No response
I've come across a strange issue where the tokens route return 404 in prod environment, but work alright in local environment.
set APP_ENV=prod in .env, and the tokens route will be invisible
3.2
8.2.18
11.9.2
Linux
No response
Hi sorry to bother you. Most of my resources are under a 'Company' panel right now. After scanning the readme, I didn't see whether it would work for resources under a different panel, like 'company' or not. Are there setup required in this case? thanks.
just a question.
1.0.0
8.1
10
No response
No response
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.