Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Because there are many breaking changes an upgrade is not that easy. There are m

- Dropped support for laravel passport
- Now you have to explicitly define the `allowRestify` method in the model policy, by default Restify don't allow you to use repositories.
- `viewAny` policy is not used anymore, you can delete it.
- `viewAny` policy isn't used anymore, you can delete it.
- The default exception handler is the Laravel one, see `restify.php -> handler`
- `fillCallback` signature has changed
- By default it will do not allow you to attach `belongsToMany` and `morphToMany` relationships. You will have to add `BelongsToMany` or `MorphToMany` field into your repository
Expand Down
2 changes: 1 addition & 1 deletion config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

'password_reset_url' => env('FRONTEND_APP_URL').'/password/reset?token={token}&email={email}',

'user_verify_url' => env('FRONTEND_APP_URL').'/verify?id={id}&hash={emailHash}',
'user_verify_url' => env('FRONTEND_APP_URL').'/verify/{id}/{emailHash}',
],

/*
Expand Down
12 changes: 11 additions & 1 deletion docs/docs/4.0/auth/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ You'll finally enjoy the auth setup (`register`, `login`, `forgot` and `reset pa

- Migrate the `personal_access_tokens` table, provided by sanctum.

- Install laravel sanctum. See the docs [here](https://laravel.com/docs/sanctum#installation). You don't need to add `\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,` in your `'api'` middleware group. So you only need to run these 3 commands:

```shell script
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
```

- Make sure your authenticatable entity (usually `App\Models\User`) implements: `Illuminate\Contracts\Auth\Authenticatable` (or simply extends the `Illuminate\Foundation\Auth\User` class as it does into a fresh laravel app.)

- Make sure the `App\Models\User` model implements the `Binaryk\LaravelRestify\Contracts\Sanctumable` contract.

- Add `\Laravel\Sanctum\HasApiTokens` trait to your `User` model.

## Define routes

Restify provides you a simple way to add all of your auth routes ready. Simply add in your `routes/api.php`:
Expand Down Expand Up @@ -137,7 +147,7 @@ This method could look like this:

public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
$this->notify(new \Binaryk\LaravelRestify\Notifications\VerifyEmail);
}
```

Expand Down
6 changes: 6 additions & 0 deletions docs/docs/4.0/custom-filters/custom-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,9 @@ The response will look like this:
]
```

Along with custom filters, you can also include in the response the primary filters (as matches), by using `?include` query param:

```http request
/api/restify/posts/filters?include=matches,searchables,sortables
```

32 changes: 30 additions & 2 deletions docs/docs/4.0/filtering/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ request:
GET: /api/restify/posts?search="Test title"
```

### Get available searchables

You can use the following request to available searchable attributes for a repository:

```http request
/api/restify/posts/filters?only=searchables
```

## Match

Matching by specific attributes may be useful if you want an exact matching.
Expand Down Expand Up @@ -187,6 +195,14 @@ The next step is to associate this class with the match key name in your `$match
];
```

### Get available matches

You can use the following request to get all repository matches:

```http request
/api/restify/posts/filters?only=matches
```

## Sort
When index query entities, usually we have to sort by specific attributes.
This requires the `$sort` configuration:
Expand Down Expand Up @@ -217,10 +233,22 @@ or with plus sign before the field:
GET: /api/restify/posts?sort=+id
```

### Get available sorts

You can use the following request to get sortable attributes for a repository:

```http request
/api/restify/posts/filters?only=sortables
```

:::tip All filters
You can use `/api/restify/posts/filters?only=sortables` request, and concatenate: `?only=sortables,matches, searchables` to get all of them at once.
:::

## Eager loading - aka withs

When get a repository index or details about a single entity, often we have to get the related entities (we have access to).
This eager loading is configurable by Restify as follow:
This eager loading is configurable by Restify as following:

```php
public static $related = ['posts'];
Expand All @@ -229,7 +257,7 @@ public static $related = ['posts'];
This means that we could use `posts` query for eager loading posts:

```http request
GET: /api/restify/users?with=posts
GET: /api/restify/users?related=posts
```

## Pagination
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/4.0/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ One important configuration is the restify default middlewares:

### Sanctum authorization

Usually you want to authorize your api (allow access only to authenticated users). For this purpose you can simply add another middleware. For the `sanctum`, Restify provides `Binaryk\LaravelRestify\Http\Middleware\RestifySanctumAuthenticate` middleware.
Usually you want to authorize your api (allow access only to authenticated users). For this purpose you can simply add another middleware. For the `sanctum`, Restify provides `Binaryk\LaravelRestify\Http\Middleware\RestifySanctumAuthenticate::class` middleware. Make sure you put this right after `api` middleware.

You may notice that Restify also use the `EnsureJsonApiHeaderMiddleware` middleware, which enforce you to use the `application/vnd.api+json` Accept header for your API requests. So make sure, even when using Postman (or something else) for making requests, that this `Accept header` is applied.

### Exception Handling

Expand Down
14 changes: 7 additions & 7 deletions docs/docs/4.0/repository-pattern/repository-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ Having this in place you're basically ready for the CRUD actions over posts. You
| GET | `/api/restify/posts/{post}` | show |
| POST | `/api/restify/posts` | store |
| POST | `/api/restify/posts/bulk` | store multiple |
| POST | `/api/restify/posts/bulk/update` | store multiple |
| PATCH | `/api/restify/posts/{post}` | update |
| PUT | `/api/restify/posts/{post}` | update |
| POST | `/api/restify/posts/{post}` | update |
| POST | `/api/restify/posts/bulk/update` | update multiple |
| PATCH | `/api/restify/posts/{post}` | partial update |
| PUT | `/api/restify/posts/{post}` | full update |
| POST | `/api/restify/posts/{post}` | partial of full update including attachments |
| DELETE | `/api/restify/posts/{post}` | destroy |

:::tip Update with files As you can see we provide 3 Verbs for the model update (PUT, PATCH, POST), the reason of that
Expand Down Expand Up @@ -195,14 +195,14 @@ You can customize the `meta` by creating your own `resolveShowMeta` method:
}
```

:::tip Resource property In the previous example we have used the `$this->resource` call, well, keep in mind, that you
:::tip $resource property
In the previous example we have used the `$this->resource` call, well, keep in mind, that you
always have access to the current resource in your not static methods of the repository, were the resource is the actual
current model. In the case above, the `$this->resource` represents the `Post` model with the `id=1`, because we're
looking for the route: `/api/restify/posts/1`. A similar way to get the model is the `$this->model()` method.
:::

Well, a lot of methods to modify the serialization partials, however, you are free to customize the entire response at
once by defining:
As we saw before, there are many ways to partially modify the response (ie separate way to modify meta), however, you are free to customize the entire response at once by defining:

```php
// PostRepository.php
Expand Down
24 changes: 24 additions & 0 deletions src/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Binaryk\LaravelRestify\Traits\Make;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use JsonSerializable;

abstract class Filter implements JsonSerializable
Expand All @@ -18,6 +19,8 @@ abstract class Filter implements JsonSerializable

public $canSeeCallback;

public static $uriKey;

public function __construct()
{
$this->booted();
Expand Down Expand Up @@ -73,10 +76,31 @@ public function resolve(RestifyRequest $request, $filter)
$this->value = $filter;
}

/**
* Get the URI key for the filter.
*
* @return string
*/
public static function uriKey()
{
if (property_exists(static::class, 'uriKey') && is_string(static::$uriKey)) {
return static::$uriKey;
}

$kebabWithoutRepository = Str::kebab(Str::replaceLast('Filter', '', class_basename(get_called_class())));

/**
* e.g. UserRepository => users
* e.g. LaravelEntityRepository => laravel-entities.
*/
return Str::plural($kebabWithoutRepository);
}

public function jsonSerialize()
{
return [
'class' => static::class,
'key' => static::uriKey(),
'type' => $this->getType(),
'options' => collect($this->options(app(Request::class)))->map(function ($value, $key) {
return is_array($value) ? ($value + ['property' => $key]) : ['label' => $key, 'property' => $value];
Expand Down
46 changes: 46 additions & 0 deletions src/Filters/MatchFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Binaryk\LaravelRestify\Filters;

use Binaryk\LaravelRestify\Filter;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Repositories\Repository;
use Illuminate\Support\Collection;

class MatchFilter extends Filter
{
public $column = 'id';

public static $uriKey = 'matches';

public function filter(RestifyRequest $request, $query, $value)
{
//@todo improve this
$query->where($this->column, $value);
}

public static function makeFromSimple($column, $type): self
{
return tap(new static, function (MatchFilter $filter) use ($column, $type) {
$filter->type = $type;
$filter->column = $column;
});
}

public static function makeForRepository(Repository $repository): Collection
{
return collect($repository::getMatchByFields())->map(function ($type, $column) {
return static::makeFromSimple($column, $type);
})->values();
}

public function jsonSerialize()
{
return [
'class' => static::class,
'type' => $this->getType(),
'key' => static::uriKey(),
'column' => $this->column,
];
}
}
44 changes: 44 additions & 0 deletions src/Filters/SearchableFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Binaryk\LaravelRestify\Filters;

use Binaryk\LaravelRestify\Filter;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Repositories\Repository;
use Illuminate\Support\Collection;

class SearchableFilter extends Filter
{
public $column = 'id';

public static $uriKey = 'searchables';

public function filter(RestifyRequest $request, $query, $value)
{
//@todo improve this
$query->where($this->column, 'LIKE', "%{$value}%");
}

public static function makeFromSimple($column): self
{
return tap(new static, function (SearchableFilter $filter) use ($column) {
$filter->column = $column;
});
}

public static function makeForRepository(Repository $repository): Collection
{
return collect($repository::getSearchableFields())->map(function ($column) {
return static::makeFromSimple($column);
});
}

public function jsonSerialize()
{
return [
'class' => static::class,
'key' => static::uriKey(),
'column' => $this->column,
];
}
}
44 changes: 44 additions & 0 deletions src/Filters/SortableFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Binaryk\LaravelRestify\Filters;

use Binaryk\LaravelRestify\Filter;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Repositories\Repository;
use Illuminate\Support\Collection;

class SortableFilter extends Filter
{
public $column = 'id';

public static $uriKey = 'sortables';

public function filter(RestifyRequest $request, $query, $direction)
{
//@todo improve this
$query->orderBy($this->column, $direction);
}

public static function makeFromSimple($column): self
{
return tap(new static, function (SortableFilter $filter) use ($column) {
$filter->column = $column;
});
}

public static function makeForRepository(Repository $repository): Collection
{
return collect($repository::getOrderByFields())->map(function ($column) {
return static::makeFromSimple($column);
});
}

public function jsonSerialize()
{
return [
'class' => static::class,
'key' => static::uriKey(),
'column' => $this->column,
];
}
}
25 changes: 24 additions & 1 deletion src/Http/Controllers/RepositoryFilterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,37 @@

namespace Binaryk\LaravelRestify\Http\Controllers;

use Binaryk\LaravelRestify\Filters\MatchFilter;
use Binaryk\LaravelRestify\Filters\SearchableFilter;
use Binaryk\LaravelRestify\Filters\SortableFilter;
use Binaryk\LaravelRestify\Http\Requests\RepositoryFiltersRequest;
use Illuminate\Support\Collection;

class RepositoryFilterController extends RepositoryController
{
public function __invoke(RepositoryFiltersRequest $request)
{
$repository = $request->repository();

return $this->response()->data($repository->availableFilters($request));
return $this->response()->data(
$repository->availableFilters($request)
// After
->when($request->has('include'), function (Collection $collection) use ($repository, $request) {
return $collection->merge(
collect(str_getcsv($request->input('include')))->map(fn ($key) => collect([
SearchableFilter::uriKey() => SearchableFilter::class,
MatchFilter::uriKey() => MatchFilter::class,
SortableFilter::uriKey() => SortableFilter::class,
])->get($key))->flatMap(fn ($filterable) => $filterable::makeForRepository($repository))
);
})
->when($request->has('only'), function (Collection $collection) use ($repository, $request) {
return collect(str_getcsv($request->input('only')))->map(fn ($key) => collect([
SearchableFilter::uriKey() => SearchableFilter::class,
MatchFilter::uriKey() => MatchFilter::class,
SortableFilter::uriKey() => SortableFilter::class,
])->get($key))->flatMap(fn ($filterable) => $filterable::makeForRepository($repository));
})
);
}
}
Loading