Skip to content

Commit 0f282ed

Browse files
authored
Prefixing repositories. (#241)
* Prefixing * Apply fixes from StyleCI (#239) * Blocking default request if prefix * Apply fixes from StyleCI (#240) * Add tests * Apply fixes from StyleCI (#242) * wip
1 parent fd8af59 commit 0f282ed

File tree

9 files changed

+203
-10
lines changed

9 files changed

+203
-10
lines changed

docs/docs/3.0/repository-pattern/repository-pattern.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,38 @@ class Post extends Repository
9494

9595
:::
9696

97+
## Repository prefix
98+
99+
Restify generates the URI for the repository in the following way:
100+
101+
```php
102+
config('restify.base') . '/' . UserRepository::uriKey() . '/'
103+
```
104+
105+
For example, let's assume we have the `restify.base` equal with: `api/restify`, the default URI generated for the UserRepository is:
106+
107+
```http request
108+
GET: /api/restify/users
109+
```
110+
111+
However, you can prefix the repository with your own:
112+
113+
```php
114+
// UserRepository
115+
public static $prefix = 'api/v1';
116+
```
117+
118+
Now, the generated URI will look like this:
119+
120+
```http request
121+
GET: /api/v1/users
122+
```
123+
124+
:::tip
125+
For the rest of the repositories the prefix will stay as it is, the default one.
126+
127+
Keep in mind that this custom prefix, will be used for all the endpoints related to the user repository.
128+
:::
97129

98130
## Repository middleware
99131

@@ -712,3 +744,5 @@ You can handle the repository boot, by using the `booted` static method:
712744
````
713745

714746

747+
748+

src/Http/Middleware/RestifyInjector.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class RestifyInjector
1717
/**
1818
* Handle an incoming request.
1919
*
20-
* @param \Illuminate\Http\Request $request
21-
* @param \Closure $next
20+
* @param \Illuminate\Http\Request $request
21+
* @param \Closure $next
2222
* @return mixed
2323
*/
2424
public function handle($request, Closure $next)
@@ -27,7 +27,13 @@ public function handle($request, Closure $next)
2727

2828
$isRestify = $request->is($path) ||
2929
$request->is(trim($path.'/*', '/')) ||
30-
$request->is('restify-api/*');
30+
$request->is('restify-api/*') ||
31+
collect(Restify::$repositories)
32+
->filter(fn ($repository) => $repository::prefix())
33+
->some(fn ($repository) => $request->is($repository::prefix().'/*')) ||
34+
collect(Restify::$repositories)
35+
->filter(fn ($repository) => $repository::indexPrefix())
36+
->some(fn ($repository) => $request->is($repository::indexPrefix().'/*'));
3137

3238
app()->register(RestifyCustomRoutesProvider::class);
3339

src/Http/Requests/InteractWithRepositories.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,17 @@ public function repository($key = null): ?Repository
4646
}
4747

4848
if (! $repository::authorizedToUseRepository($this)) {
49-
throw new UnauthorizedException(__('Unauthorized to view repository :name. See "allowRestify" policy.', [
49+
throw new UnauthorizedException(__('Unauthorized to view repository :name. Check "allowRestify" policy.', [
5050
'name' => $repository,
5151
]), 403);
5252
}
5353

54+
if (! $repository::authorizedToUseRoute($this)) {
55+
abort(403, __('Unauthorized to use the route :name. Check prefix.', [
56+
'name' => $this->getRequestUri(),
57+
]));
58+
}
59+
5460
app(Pipeline::class)
5561
->send($this)
5662
->through(optional($repository::collectMiddlewares($this))->toArray())

src/Repositories/Repository.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ abstract class Repository implements RestifySearchable, JsonSerializable
4040
ConditionallyLoadsAttributes,
4141
DelegatesToResource,
4242
ResolvesActions,
43-
RepositoryEvents;
43+
RepositoryEvents,
44+
WithRoutePrefix;
4445

4546
/**
4647
* This is named `resource` because of the forwarding properties from DelegatesToResource trait.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Repositories;
4+
5+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
6+
use Illuminate\Support\Str;
7+
8+
trait WithRoutePrefix
9+
{
10+
/**
11+
* The repository routes default prefix.
12+
*
13+
* @var string
14+
*/
15+
public static $prefix;
16+
17+
/**
18+
* The repository index route default prefix.
19+
* @var string
20+
*/
21+
public static $indexPrefix;
22+
23+
public static function prefix(): ?string
24+
{
25+
return static::sanitizeSlashes(
26+
static::$prefix
27+
);
28+
}
29+
30+
public static function indexPrefix(): ?string
31+
{
32+
return static::sanitizeSlashes(
33+
static::$indexPrefix
34+
);
35+
}
36+
37+
protected static function sanitizeSlashes(?string $prefix): ?string
38+
{
39+
if ($prefix && Str::startsWith($prefix, '/')) {
40+
$prefix = Str::replaceFirst('/', '', $prefix);
41+
}
42+
43+
if ($prefix && Str::endsWith($prefix, '/')) {
44+
$prefix = Str::replaceLast('/', '', $prefix);
45+
}
46+
47+
return $prefix;
48+
}
49+
50+
public static function authorizedToUseRoute(RestifyRequest $request): bool
51+
{
52+
if (! static::shouldAuthorizeRouteUsage()) {
53+
return true;
54+
}
55+
56+
if ($request->isForRepositoryRequest()) {
57+
// index
58+
if (static::indexPrefix()) {
59+
return $request->is(static::indexPrefix().'/*');
60+
}
61+
62+
if (static::prefix()) {
63+
return $request->is(static::prefix().'/*');
64+
}
65+
} else {
66+
// the rest
67+
return $request->is(static::prefix().'/*');
68+
}
69+
}
70+
71+
protected static function shouldAuthorizeRouteUsage(): bool
72+
{
73+
return collect([
74+
static::prefix(),
75+
static::indexPrefix(),
76+
])->some(fn ($prefix) => (bool) $prefix);
77+
}
78+
}

src/RestifyServiceProvider.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Binaryk\LaravelRestify;
44

5+
use Binaryk\LaravelRestify\Http\Controllers\RepositoryIndexController;
56
use Illuminate\Contracts\Debug\ExceptionHandler;
67
use Illuminate\Support\Facades\Route;
78
use Illuminate\Support\ServiceProvider;
@@ -35,7 +36,9 @@ protected function registerRoutes()
3536
'middleware' => config('restify.middleware', []),
3637
];
3738

38-
$this->defaultRoutes($config);
39+
$this->defaultRoutes($config)
40+
->registerPrefixed($config)
41+
->registerIndexPrefixed($config);
3942
}
4043

4144
/**
@@ -51,6 +54,38 @@ public function defaultRoutes($config)
5154
return $this;
5255
}
5356

57+
/**
58+
* @param $config
59+
* @return $this
60+
*/
61+
public function registerPrefixed($config)
62+
{
63+
collect(Restify::$repositories)
64+
->filter(fn ($repository) => $repository::prefix())
65+
->each(function ($repository) use ($config) {
66+
$config['prefix'] = $repository::prefix();
67+
Route::group($config, function () {
68+
$this->loadRoutesFrom(__DIR__.'/../routes/api.php');
69+
});
70+
});
71+
72+
return $this;
73+
}
74+
75+
public function registerIndexPrefixed($config)
76+
{
77+
collect(Restify::$repositories)
78+
->filter(fn ($repository) => $repository::indexPrefix())
79+
->each(function ($repository) use ($config) {
80+
$config['prefix'] = $repository::indexPrefix();
81+
Route::group($config, function () {
82+
Route::get('/{repository}', '\\'.RepositoryIndexController::class);
83+
});
84+
});
85+
86+
return $this;
87+
}
88+
5489
/**
5590
* Register Restify's custom exception handler.
5691
*

tests/Fixtures/User/UserRepository.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
use Binaryk\LaravelRestify\Repositories\Repository;
99
use Binaryk\LaravelRestify\Repositories\UserProfile;
1010

11-
/**
12-
* @author Eduard Lupacescu <[email protected]>
13-
*/
1411
class UserRepository extends Repository
1512
{
1613
use UserProfile;

tests/IntegrationTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ abstract class IntegrationTest extends TestCase
4242

4343
protected function setUp(): void
4444
{
45+
$this->loadRepositories();
4546
parent::setUp();
4647
DB::enableQueryLog();
4748

@@ -52,7 +53,6 @@ protected function setUp(): void
5253
$this->loadRoutes();
5354
$this->withFactories(__DIR__.'/Factories');
5455
$this->injectTranslator();
55-
$this->loadRepositories();
5656
$this->app->bind(ExceptionHandler::class, RestifyHandler::class);
5757
}
5858

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Repositories;
4+
5+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository;
6+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
7+
8+
class RepositoryCustomPrefixTest extends IntegrationTest
9+
{
10+
protected function setUp(): void
11+
{
12+
PostRepository::$prefix = 'api/restify-api/v1';
13+
14+
PostRepository::$indexPrefix = 'api/restify-api/index';
15+
16+
parent::setUp();
17+
}
18+
19+
public function test_repository_can_have_custom_prefix()
20+
{
21+
$this->getJson('api/restify-api/index/'.PostRepository::uriKey())
22+
->assertSuccessful();
23+
}
24+
25+
public function test_repository_prefix_block_default_route()
26+
{
27+
$this->getJson('/restify-api/'.PostRepository::uriKey())
28+
->assertForbidden();
29+
30+
$this->getJson('api/restify-api/index/'.PostRepository::uriKey())
31+
->assertSuccessful();
32+
33+
$this->postJson('/restify-api/'.PostRepository::uriKey())
34+
->assertForbidden();
35+
}
36+
}

0 commit comments

Comments
 (0)