diff --git a/docs/docs/3.0/repository-pattern/repository-pattern.md b/docs/docs/3.0/repository-pattern/repository-pattern.md index 359d5e81f..030f131b7 100644 --- a/docs/docs/3.0/repository-pattern/repository-pattern.md +++ b/docs/docs/3.0/repository-pattern/repository-pattern.md @@ -94,6 +94,38 @@ class Post extends Repository ::: +## Repository prefix + +Restify generates the URI for the repository in the following way: + +```php +config('restify.base') . '/' . UserRepository::uriKey() . '/' +``` + +For example, let's assume we have the `restify.base` equal with: `api/restify`, the default URI generated for the UserRepository is: + +```http request +GET: /api/restify/users +``` + +However, you can prefix the repository with your own: + +```php +// UserRepository +public static $prefix = 'api/v1'; +``` + +Now, the generated URI will look like this: + +```http request +GET: /api/v1/users +``` + +:::tip +For the rest of the repositories the prefix will stay as it is, the default one. + +Keep in mind that this custom prefix, will be used for all the endpoints related to the user repository. +::: ## Repository middleware @@ -712,3 +744,5 @@ You can handle the repository boot, by using the `booted` static method: ```` + + diff --git a/src/Http/Middleware/RestifyInjector.php b/src/Http/Middleware/RestifyInjector.php index 0a5f555aa..f6b450ce9 100644 --- a/src/Http/Middleware/RestifyInjector.php +++ b/src/Http/Middleware/RestifyInjector.php @@ -17,8 +17,8 @@ class RestifyInjector /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) @@ -27,7 +27,13 @@ public function handle($request, Closure $next) $isRestify = $request->is($path) || $request->is(trim($path.'/*', '/')) || - $request->is('restify-api/*'); + $request->is('restify-api/*') || + collect(Restify::$repositories) + ->filter(fn ($repository) => $repository::prefix()) + ->some(fn ($repository) => $request->is($repository::prefix().'/*')) || + collect(Restify::$repositories) + ->filter(fn ($repository) => $repository::indexPrefix()) + ->some(fn ($repository) => $request->is($repository::indexPrefix().'/*')); app()->register(RestifyCustomRoutesProvider::class); diff --git a/src/Http/Requests/InteractWithRepositories.php b/src/Http/Requests/InteractWithRepositories.php index f632c3818..210e4dbe0 100644 --- a/src/Http/Requests/InteractWithRepositories.php +++ b/src/Http/Requests/InteractWithRepositories.php @@ -46,11 +46,17 @@ public function repository($key = null): ?Repository } if (! $repository::authorizedToUseRepository($this)) { - throw new UnauthorizedException(__('Unauthorized to view repository :name. See "allowRestify" policy.', [ + throw new UnauthorizedException(__('Unauthorized to view repository :name. Check "allowRestify" policy.', [ 'name' => $repository, ]), 403); } + if (! $repository::authorizedToUseRoute($this)) { + abort(403, __('Unauthorized to use the route :name. Check prefix.', [ + 'name' => $this->getRequestUri(), + ])); + } + app(Pipeline::class) ->send($this) ->through(optional($repository::collectMiddlewares($this))->toArray()) diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index bab6812ca..b504eb2a0 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -40,7 +40,8 @@ abstract class Repository implements RestifySearchable, JsonSerializable ConditionallyLoadsAttributes, DelegatesToResource, ResolvesActions, - RepositoryEvents; + RepositoryEvents, + WithRoutePrefix; /** * This is named `resource` because of the forwarding properties from DelegatesToResource trait. diff --git a/src/Repositories/WithRoutePrefix.php b/src/Repositories/WithRoutePrefix.php new file mode 100644 index 000000000..959f5b19d --- /dev/null +++ b/src/Repositories/WithRoutePrefix.php @@ -0,0 +1,78 @@ +isForRepositoryRequest()) { + // index + if (static::indexPrefix()) { + return $request->is(static::indexPrefix().'/*'); + } + + if (static::prefix()) { + return $request->is(static::prefix().'/*'); + } + } else { + // the rest + return $request->is(static::prefix().'/*'); + } + } + + protected static function shouldAuthorizeRouteUsage(): bool + { + return collect([ + static::prefix(), + static::indexPrefix(), + ])->some(fn ($prefix) => (bool) $prefix); + } +} diff --git a/src/RestifyServiceProvider.php b/src/RestifyServiceProvider.php index 4309cd415..2baafccee 100644 --- a/src/RestifyServiceProvider.php +++ b/src/RestifyServiceProvider.php @@ -2,6 +2,7 @@ namespace Binaryk\LaravelRestify; +use Binaryk\LaravelRestify\Http\Controllers\RepositoryIndexController; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; @@ -35,7 +36,9 @@ protected function registerRoutes() 'middleware' => config('restify.middleware', []), ]; - $this->defaultRoutes($config); + $this->defaultRoutes($config) + ->registerPrefixed($config) + ->registerIndexPrefixed($config); } /** @@ -51,6 +54,38 @@ public function defaultRoutes($config) return $this; } + /** + * @param $config + * @return $this + */ + public function registerPrefixed($config) + { + collect(Restify::$repositories) + ->filter(fn ($repository) => $repository::prefix()) + ->each(function ($repository) use ($config) { + $config['prefix'] = $repository::prefix(); + Route::group($config, function () { + $this->loadRoutesFrom(__DIR__.'/../routes/api.php'); + }); + }); + + return $this; + } + + public function registerIndexPrefixed($config) + { + collect(Restify::$repositories) + ->filter(fn ($repository) => $repository::indexPrefix()) + ->each(function ($repository) use ($config) { + $config['prefix'] = $repository::indexPrefix(); + Route::group($config, function () { + Route::get('/{repository}', '\\'.RepositoryIndexController::class); + }); + }); + + return $this; + } + /** * Register Restify's custom exception handler. * diff --git a/tests/Fixtures/User/UserRepository.php b/tests/Fixtures/User/UserRepository.php index 45bef9a5a..8819b2fc9 100644 --- a/tests/Fixtures/User/UserRepository.php +++ b/tests/Fixtures/User/UserRepository.php @@ -8,9 +8,6 @@ use Binaryk\LaravelRestify\Repositories\Repository; use Binaryk\LaravelRestify\Repositories\UserProfile; -/** - * @author Eduard Lupacescu - */ class UserRepository extends Repository { use UserProfile; diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 3c4a4d59f..4fd44e7c1 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -42,6 +42,7 @@ abstract class IntegrationTest extends TestCase protected function setUp(): void { + $this->loadRepositories(); parent::setUp(); DB::enableQueryLog(); @@ -52,7 +53,6 @@ protected function setUp(): void $this->loadRoutes(); $this->withFactories(__DIR__.'/Factories'); $this->injectTranslator(); - $this->loadRepositories(); $this->app->bind(ExceptionHandler::class, RestifyHandler::class); } diff --git a/tests/Repositories/RepositoryCustomPrefixTest.php b/tests/Repositories/RepositoryCustomPrefixTest.php new file mode 100644 index 000000000..150f0580c --- /dev/null +++ b/tests/Repositories/RepositoryCustomPrefixTest.php @@ -0,0 +1,36 @@ +getJson('api/restify-api/index/'.PostRepository::uriKey()) + ->assertSuccessful(); + } + + public function test_repository_prefix_block_default_route() + { + $this->getJson('/restify-api/'.PostRepository::uriKey()) + ->assertForbidden(); + + $this->getJson('api/restify-api/index/'.PostRepository::uriKey()) + ->assertSuccessful(); + + $this->postJson('/restify-api/'.PostRepository::uriKey()) + ->assertForbidden(); + } +}