diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index bcef1e03..70fa5b5e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -39,5 +39,11 @@ jobs:
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
+ - name: Clear Composer cache
+ run: composer clear-cache
+
+ - name: Wait for a few seconds
+ run: sleep 5
+
- name: Execute tests
run: ./vendor/bin/testbench package:test --parallel --no-coverage
diff --git a/docs-v2/content/en/api/rest-methods.md b/docs-v2/content/en/api/rest-methods.md
index df90f7de..94b8d005 100644
--- a/docs-v2/content/en/api/rest-methods.md
+++ b/docs-v2/content/en/api/rest-methods.md
@@ -1,4 +1,9 @@
---- title: REST Methods menuTitle: Controllers category: API position: 12 ---
+---
+title: REST Methods
+menuTitle: Controllers
+category: API
+position: 12
+---
## Introduction
diff --git a/docs-v2/content/en/auth/authentication.md b/docs-v2/content/en/auth/authentication.md
index 8ddc11f7..e27101cf 100644
--- a/docs-v2/content/en/auth/authentication.md
+++ b/docs-v2/content/en/auth/authentication.md
@@ -13,6 +13,12 @@ Now you can finally enjoy the auth setup (`register`, `login`, `forgot`, and `re
Migrate the `users`, `password_resets` table (they already exist into a fresh Laravel app).
+
+
+Laravel 10 automatically ships with Sanctum, so you don't have to install it.
+
+
+
### Install 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.
@@ -42,7 +48,8 @@ The `User` model should extend the `Illuminate\Foundation\Auth\User` class or im
-Make sure you didn't skip adding the `\Laravel\Sanctum\HasApiTokens` trait to your `User` model.
+Make sure you have the `\Laravel\Sanctum\HasApiTokens` trait to your `User` model.
+Laravel 10 will automatically add this trait to your `User` model.
@@ -66,17 +73,17 @@ Restify provides you a simple way to add all of your auth routes prepared. Simpl
Route::restifyAuth();
```
-And voila, now you have auth routes ready to be used.
+And voilĂ , now you have auth routes ready to be used.
These are the default routes provided by restify:
-| Verb | URI | Action |
-| :------------- |:-----------------------------------------| :----------------|
-| **POST** | `/api/register` | register |
-| **POST** | `/api/login` | login |
-| **POST** | `/api/restify/forgotPassword` | forgot password |
-| **POST** | `/api/restify/resetPassword` | reset password |
-| **POST** | `/api/restify/verify/{id}/{emailHash}` | verify user |
+| Verb | URI | Action |
+| :------------- |:-----------------------------------------|:---------------|
+| **POST** | `/api/register` | register |
+| **POST** | `/api/login` | login |
+| **POST** | `/api/restify/forgotPassword` | forgotPassword |
+| **POST** | `/api/restify/resetPassword` | resetPassword |
+| **POST** | `/api/restify/verify/{id}/{emailHash}` | verifyEmail |
@@ -84,14 +91,15 @@ The `register` and `login` routes are outside the base `restify` prefix because
-## Export auth controllers
-All of these routes are handled by default, so you can just use them facilely. However, you can customize each of them by exporting auth controllers:
+You can also pass an `actions` argument, which is an array of actions you want to register. For example:
-```shell
-php artisan restify:auth
+```php
+Route::restifyAuth(actions: ['login', 'register']);
```
-Now you have all the auth controllers and blade email files exported into your project.
+
+By using the `actions` argument, only the specified routes will be registered. If no `actions` argument is passed, Restify will register all the routes by default.
+
## Sanctum Middleware
@@ -105,3 +113,256 @@ Next, add the `auth:sanctum` middleware after the `api` middleware in your confi
...
],
```
+
+## Login
+
+Let's ensure the authentication is working correctly. Create a user in the DatabaseSeeder class:
+
+```php
+// DatabaseSeeder.php
+\App\Models\User::factory()->create([
+ 'name' => 'Test User',
+ 'email' => 'test@example.com',
+ 'password' => \Illuminate\Support\Facades\Hash::make('password'),
+]);
+```
+
+Seed it:
+
+```shell
+php artisan db:seed
+```
+
+Now you can test the login with Curl or Postman:
+
+```shell
+curl -X POST "http://restify-app.test/api/login" \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "test@example.com",
+ "password": "password"
+ }'
+```
+
+So you should see the response like this:
+
+```json
+{
+ "id": "11",
+ "type": "users",
+ "attributes": {
+ "name": "Test User",
+ "email": "test@example.com"
+ },
+ "meta": {
+ "authorizedToShow": true,
+ "authorizedToStore": false,
+ "authorizedToUpdate": false,
+ "authorizedToDelete": false,
+ "token": "1|f7D1qkALtM9GKDkjREKpwMRKTZg2ZnFqDZTSe53k"
+ }
+}
+```
+
+## Register
+
+Let's see how to register a new user in the application. You can test the registration using Curl or Postman.
+
+Use the following endpoint for registration:
+
+`http://restify-app.test/api/register`
+
+And send this payload:
+
+```json
+{
+ "name": "John Doe",
+ "email": "demo@restify.com",
+ "password": "secret!",
+ "password_confirmation": "secret!"
+}
+```
+
+Note: Email and password fields are required.
+
+Now, you can send a POST request with Curl:
+
+```shell
+curl -X POST "http://restify-app.test/api/register" \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "John Doe",
+ "email": "demo@restify.com",
+ "password": "secret!",
+ "password_confirmation": "secret!"
+ }'
+```
+
+You should see the response like this:
+
+```json
+{
+ "id": "12",
+ "type": "users",
+ "attributes": {
+ "name": "John Doe",
+ "email": "demo@restify.com"
+ },
+ "meta": {
+ "authorizedToShow": true,
+ "authorizedToStore": false,
+ "authorizedToUpdate": false,
+ "authorizedToDelete": false,
+ "token": "2|z8D2rkBLtN8GKDkjREKpwMRKTZg2ZnFqDZTSe53k"
+ }
+}
+```
+
+## Forgot Password
+
+To initiate the password reset process, use the following endpoint:
+
+`{{host}}/api/forgotPassword`
+
+And send this payload:
+
+```json
+{
+ "email": "demo@restify.com"
+}
+```
+
+After making a POST request to this endpoint, an email will be sent to the provided email address containing a link to reset the password. The link looks like this:
+
+`'password_reset_url' => env('FRONTEND_APP_URL').'/password/reset?token={token}&email={email}',`
+
+This configuration can be found in the `config/restify.php` file. The FRONTEND_APP_URL should be set to the URL of your frontend app, where the user lands when they click the action button in the email. The "token" is a variable that will be used to reset the password later on.
+
+To view the email content during development, you can change the following configuration in your .env file:
+
+```dotenv
+MAIL_MAILER=log
+```
+
+This will log the email content to the `laravel.log` file, allowing you to see the password reset email without actually sending it.
+
+Now, you can send a POST request with Curl:
+
+```shell
+curl -X POST "http://restify-app.test/api/forgotPassword" \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "demo@restify.com"
+ }'
+```
+
+If the email is successfully sent, you'll receive a response similar to the following:
+
+```json
+{
+ "message": "Reset password link sent to your email."
+}
+```
+
+Now, the user can follow the link in the email to reset their password.
+
+
+## Reset Password
+
+After the user has received the password reset email from the Forgot Password process, they can reset their password using the following endpoint:
+
+`http://restify-app.test/api/resetPassword`
+
+The payload should include the token and email received from the password reset email:
+
+```json
+{
+ "token": "7e474bb9118e736306de27126343644a7cb0ecdaec558fdef30946d15225bc07",
+ "email": "demo@restify.com",
+ "password": "new_password",
+ "password_confirmation": "new_password"
+}
+```
+Now, you can send a POST request with Curl:
+
+```shell
+curl -X POST "http://restify-app.test/api/resetPassword" \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "token": "0d20b6cfa48f2bbbb83bf913d5e329207149f74d7b22d59a383d321c7af7fd5e",
+ "email": "demo@restify.com",
+ "password": "new_password",
+ "password_confirmation": "new_password"
+ }'
+```
+
+If the password reset is successful, you should receive a response similar to the following:
+
+```json
+{
+ "message": "Your password has been successfully reset."
+}
+```
+
+Now the user's password has been successfully reset, and they can log in with their new password.
+
+
+## Customizing Authentication Controllers
+
+You can publish the authentication controllers from the Restify package to your own application, allowing you to customize their behavior as needed. To publish the controllers, run the following command:
+
+```shell
+php artisan restify:auth
+```
+
+This command will copy the authentication controllers to the `app/Http/Controllers/Restify` directory in your Laravel project.
+
+The command accepts an optional `--actions` parameter, which allows you to specify which controllers you want to publish. If no action is passed, the command will publish all controllers and the `ForgotPasswordNotification`. For example, to publish only the `login` and `register` controllers, run:
+
+```shell
+php artisan restify:auth --actions=login,register
+```
+
+Now, you can make any necessary changes to these controllers to fit your specific requirements.
+
+### Customizing the Register Route
+
+In a real-world scenario, you might need to customize only the register route. To do this, you can use the `restify:auth` command with the `--actions` option to publish only the register controller:
+
+ ```shell
+php artisan restify:auth --actions=register
+```
+
+After running the command, the register controller will be published to your application, and you can modify it to fit your requirements.
+
+
+
+Important Note: If you want to publish other actions in the future, you'll need to manually update the `routes/api.php` file before running the restify:auth command again. Remove any previously published Restify routes, and keep the `Route::restifyAuth();` line so that the new routes can be correctly published.
+
+
+
+For example, if you previously published the register route, your `routes/api.php` file might look like this:
+
+```php
+// ...
+
+Route::restifyAuth(actions: ["login", "resetPassword", "forgotPassword", "verifyEmail"]);
+
+// ...
+```
+
+Before running the `restify:auth` command again, revert the file to its original state:
+
+```php
+// ...
+
+Route::restifyAuth();
+
+// ...
+```
+
+Now you can run the `restify:auth` command with other actions, and the routes will be published correctly.
diff --git a/docs-v2/content/en/index.md b/docs-v2/content/en/index.md
index 3f1162c6..da0b606e 100644
--- a/docs-v2/content/en/index.md
+++ b/docs-v2/content/en/index.md
@@ -9,6 +9,12 @@ Laravel Restify is an extraordinary tool inspired by [Laravel Nova](https://nova
If you don't have an application written with Nova, you can start with Laravel Restify from scratch and get a powerful API in a few minutes.
+
+
+This documentation is for the latest version of Laravel Restify. Please ensure you are using the most recent release of the package to guarantee compatibility with the information provided in this documentation. To update Laravel Restify, refer to the upgrade guide for instructions.
+
+
+
## Features
+
+
## Playground
You can find a playground in the [Restify Demo GitHub repository](https://github.com/BinarCode/restify-demo).
diff --git a/docs-v2/content/en/quickstart.md b/docs-v2/content/en/quickstart.md
index ed339a97..663b50ae 100644
--- a/docs-v2/content/en/quickstart.md
+++ b/docs-v2/content/en/quickstart.md
@@ -1,18 +1,23 @@
---
-title: Installation
+title: Quickstart
category: Getting Started
---
## Requirements
-
Laravel Restify has a few requirements that you should be mindful of before installing:
+
+
+
@@ -22,6 +27,16 @@ Laravel Restify has a few requirements that you should be mindful of before inst
composer require binaryk/laravel-restify
```
+### Package Stability
+
+
+
+If you are not able to install Restify into your application because of your `minimum-stability` setting, consider
+setting your `minimum-stability` option to `dev` and your `prefer-stable` option to `true`. This will allow you to
+install Laravel Restify while still preferring stable package releases for your application.
+
+
+
## Setup
After the installation, the package requires a setup process:
@@ -32,34 +47,53 @@ php artisan restify:setup
The command above:
-- **publishes** the `config/restify.php` configuration file
+- **publishes** the `config/restify.php` configuration file and `action_logs` table migration
- **creates** the `providers/RestifyServiceProvider` and will add it in the `config/app.php`
- **creates** a new `app/Restify` directory
- **creates** an abstract `app/Restify/Repository.php`
- **scaffolds** a `app/Restify/UserRepository` repository for users CRUD
-### Package Stability
+### Migrations
-
+After the setup, you should run the migrations:
-If you are not able to install Restify into your application because of your `minimum-stability` setting, consider
-setting your `minimum-stability` option to `dev` and your `prefer-stable` option to `true`. This will allow you to
-install Laravel Restify while still preferring stable package releases for your application.
+```shell script
+php artisan migrate
+```
-
+## Generating Mock Data
+
+To generate mock data for your database, you need to install the `doctrine/dbal` package as a development dependency:
+
+```bash
+composer require doctrine/dbal --dev
+```
+After installing the package, you can use the restify:stub command to generate mock data for a specific table:
+
+```bash
+php artisan restify:stub table_name --count=10
+```
+
+Replace table_name with the name of the table you want to generate mock data for and use the --count option to specify the number of records you want to create.
+
+For example, to generate 10 users:
+
+```shell
+php artisan restify:stub users --count=10
+```
## Quick start
-Having the package setup and users table migrated, you should be good to perform the first API request:
+Having the package setup and users table migrated and seeded, you should be good to perform the first API request:
```http request
-GET: /api/restify/users?perPage=10&page=2
+GET: /api/restify/users?perPage=10&page=1
```
or use the [json api](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#auto-id-pagesize) format:
```http request
-GET: /api/restify/users?page[size]=10&page[number]=2
+GET: /api/restify/users?page[size]=10&page[number]=1
```
This should return the users list paginated and formatted according to [JSON:API](https://jsonapi.org/format/) standard.
@@ -90,11 +124,11 @@ One important configuration is the restify's default middleware:
]
```
-### Sanctum authentication
+#### Sanctum authentication
Normally, you would want to authenticate your api (allow access only to authenticated users). For this purpose, you can simply add another middleware. For the `sanctum`, you can add the `auth:sanctum`. Make sure you put this right after `api` middleware.
-Restify also provides the `EnsureJsonApiHeaderMiddleware` middleware, which enforces you to use the `application/application-json` `Accept header` for your API requests. If you prefer to add this middleware, when using the Postman/Insomnia API client, make sure that this `Accept header` is applied.
+We will cover this more in the [Authentication](/auth/authentication) section.
## Generate repository
diff --git a/docs-v2/nuxt.config.js b/docs-v2/nuxt.config.js
index f12f694d..e37fa112 100644
--- a/docs-v2/nuxt.config.js
+++ b/docs-v2/nuxt.config.js
@@ -1,7 +1,7 @@
-import theme from "@nuxt/content-theme-docs";
+import theme from '@nuxt/content-theme-docs';
export default theme({
- docs: {
- primaryColor: '#787af6'
- },
+ docs: {
+ primaryColor: '#787af6'
+ }
});
diff --git a/src/Commands/PublishAuthCommand.php b/src/Commands/PublishAuthCommand.php
index ae5d5e62..5f789d92 100644
--- a/src/Commands/PublishAuthCommand.php
+++ b/src/Commands/PublishAuthCommand.php
@@ -9,58 +9,46 @@
class PublishAuthCommand extends Command
{
- protected $name = 'restify:auth';
+ protected $signature = 'restify:auth {--actions= : Comma-separated list of actions to publish}';
- protected $description = 'Publish auth controllers & blades.';
+ protected $description = 'Publish auth controllers & notification.';
public function handle()
{
+ $actions = $this->option('actions') ? explode(',', $this->option('actions')) : null;
+
$this
->publishControllers()
- ->publishBlades()
- ->publishEmails()
- ->registerRoutes();
+ ->publishNotifications()
+ ->registerRoutes($actions);
- $this->info('Restify Controllers & Emails published successfully');
+ $this->info('Auth controllers published.');
}
- /**
- * @return $this
- */
public function publishControllers(): self
{
$path = 'Http/Controllers/Restify/Auth/';
$stubDirectory = '/../Commands/stubs/Auth';
$format = '.php';
+ $actions = $this->option('actions') ? explode(',', $this->option('actions')) : null;
+
$this->checkDirectory($path)
- ->copyDirectory($path, $stubDirectory, $format);
+ ->copyDirectory($path, $stubDirectory, $format, $actions);
return $this;
}
- /**
- * @return $this
- */
- public function publishBlades(): self
+ public function publishNotifications(): self
{
- $path = '../resources/views/Restify/Auth/';
- $stubDirectory = '/../Commands/stubs/Blades';
- $format = '.blade.php';
+ $actions = $this->option('actions') ? explode(',', $this->option('actions')) : null;
- $this->checkDirectory($path)
- ->copyDirectory($path, $stubDirectory, $format);
-
- return $this;
- }
+ if (! empty($actions) && ! in_array('forgotPassword', $actions)) {
+ return $this;
+ }
- /**
- * @return $this
- */
- public function publishEmails(): self
- {
- $path = 'Mail/Restify/Auth/';
- $stubDirectory = '/../Commands/stubs/Email';
+ $path = 'Notifications/Restify/';
+ $stubDirectory = '/../Commands/stubs/Notifications';
$format = '.php';
$this->checkDirectory($path)
@@ -69,9 +57,6 @@ public function publishEmails(): self
return $this;
}
- /**
- * @return $this
- */
public function checkDirectory(string $path): self
{
if (! is_dir($directory = app_path($path))) {
@@ -81,14 +66,20 @@ public function checkDirectory(string $path): self
return $this;
}
- /**
- * @return $this
- */
- protected function copyDirectory(string $path, string $stubDirectory, string $format): self
+ protected function copyDirectory(string $path, string $stubDirectory, string $format, ?array $actions = []): self
{
$filesystem = new Filesystem();
collect($filesystem->allFiles(__DIR__.$stubDirectory))
+ ->filter(function (SplFileInfo $file) use ($actions) {
+ if (empty($actions)) {
+ return true;
+ }
+
+ $actionName = Str::before($file->getFilename(), 'Controller');
+
+ return in_array($actionName, $actions, true) || in_array(Str::lower($actionName), $actions, true);
+ })
->each(function (SplFileInfo $file) use ($filesystem, $path, $format, $stubDirectory) {
$filesystem->copy(
$file->getPathname(),
@@ -112,24 +103,66 @@ protected function setNamespace(string $stubDirectory, string $fileName, string
));
}
- /**
- * @return $this
- */
- protected function registerRoutes(): self
+ protected function registerRoutes(?array $actions): self
{
$apiPath = base_path('routes/api.php');
$initial = file_get_contents($apiPath);
- $initial = str($initial)->replace('Route::restifyAuth();', '')->toString();
+ $remainingActionsString = $this->getRemainingActionsString($actions);
+ $initial = str($initial)->replace('Route::restifyAuth();', $remainingActionsString)->toString();
$file = fopen($apiPath, 'w');
- $routeStub = __DIR__.'/stubs/Routes/routes.stub';
+ $routeStub = $this->getRouteStubs();
- fwrite($file, $initial."\n".file_get_contents($routeStub));
+ fwrite($file, $initial."\n".$routeStub);
fclose($file);
return $this;
}
+
+ protected function getRouteStubs(): string
+ {
+ $actions = $this->option('actions') ? explode(',', $this->option('actions')) : null;
+
+ $stubDirectory = __DIR__.'/stubs/Routes/';
+ $routes = [
+ 'login' => 'loginRoute.stub',
+ 'register' => 'registerRoute.stub',
+ 'forgotPassword' => 'forgotPasswordRoute.stub',
+ 'ForgotPassword' => 'forgotPasswordRoute.stub',
+ 'resetPassword' => 'forgotPasswordRoute.stub',
+ 'ResetPassword' => 'resetPasswordRoute.stub',
+ 'verifyEmail' => 'verifyRoute.stub',
+ 'verify' => 'verifyRoute.stub',
+ ];
+
+ $routeStubs = '';
+
+ foreach ($routes as $action => $routeStub) {
+ if (! $actions || in_array($action, $actions, true)) {
+ $routeStubs .= file_get_contents($stubDirectory.$routeStub);
+ }
+ }
+
+ return $routeStubs;
+ }
+
+ protected function getRemainingActionsString(?array $actions = null): string
+ {
+ $allActions = ['login', 'register', 'resetPassword', 'forgotPassword', 'verifyEmail'];
+
+ if ($actions === null) {
+ return 'Route::restifyAuth();';
+ }
+
+ $remainingActions = array_diff($allActions, $actions);
+
+ if (empty($remainingActions)) {
+ return '';
+ }
+
+ return 'Route::restifyAuth(actions: '.json_encode(array_values($remainingActions)).');';
+ }
}
diff --git a/src/Commands/stubs/Auth/ForgotPasswordController.stub b/src/Commands/stubs/Auth/ForgotPasswordController.stub
index 9925e9af..a0ee13cf 100644
--- a/src/Commands/stubs/Auth/ForgotPasswordController.stub
+++ b/src/Commands/stubs/Auth/ForgotPasswordController.stub
@@ -2,11 +2,11 @@
namespace {{namespace}};
-use App\Mail\Restify\Auth\ForgotPasswordMail;
use App\Models\User;
+use App\Notifications\Restify\ForgotPasswordNotification;
use Illuminate\Http\Request;
+use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Routing\Controller;
-use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
@@ -19,7 +19,7 @@ class ForgotPasswordController extends Controller
]);
/** @var User $user */
- $user = User::query()->where($request->only('email'))->firstOrFail();
+ $user = config('restify.auth.user_model')::query()->where($request->only('email'))->firstOrFail();
$token = Password::createToken($user);
@@ -29,10 +29,9 @@ class ForgotPasswordController extends Controller
$request->input('url') ?? config('restify.auth.password_reset_url')
);
- Mail::to($user->email)->send(
- new ForgotPasswordMail($url)
- );
- return data(__('Email sent.'));
+ (new AnonymousNotifiable())->route('mail', $user->email)->notify(new ForgotPasswordNotification($url));
+
+ return ok(__('Reset password link sent to your email.'));
}
}
diff --git a/src/Commands/stubs/Auth/LoginController.stub b/src/Commands/stubs/Auth/LoginController.stub
index e9633c9d..0a79d537 100644
--- a/src/Commands/stubs/Auth/LoginController.stub
+++ b/src/Commands/stubs/Auth/LoginController.stub
@@ -1,23 +1,21 @@
validate([
- 'email' => ['required', 'email', 'exists:users,email'],
+ 'email' => ['required', 'email'],
'password' => ['required'],
]);
/** * @var User $user */
-
if (! $user = config('restify.auth.user_model')::query()
->whereEmail($request->input('email'))
->first()) {
@@ -28,9 +26,8 @@ class LoginController extends Controller
abort(401, 'Invalid credentials.');
}
- return data([
- 'user' => $user,
- 'token' => $user->createToken('login'),
+ return rest($user)->indexMeta([
+ 'token' => $user->createToken('login')->plainTextToken,
]);
}
}
diff --git a/src/Commands/stubs/Auth/RegisterController.stub b/src/Commands/stubs/Auth/RegisterController.stub
index ef2a105a..ab110fc6 100644
--- a/src/Commands/stubs/Auth/RegisterController.stub
+++ b/src/Commands/stubs/Auth/RegisterController.stub
@@ -2,31 +2,34 @@
namespace {{namespace}};
-use Illuminate\Http\JsonResponse;
+use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Hash;
-use App\Models\User;
class RegisterController extends Controller
{
- public function __invoke(Request $request): JsonResponse
+ public function __invoke(Request $request)
{
$request->validate([
- 'email' => ['required', 'email', 'max:255', 'unique:' . Config::get('restify.auth.table', 'users')],
+ 'email' => ['required', 'email', 'max:255', 'unique:'.Config::get('config.auth.table', 'users')],
'password' => ['required', 'confirmed'],
]);
- $user = User::forceCreate([
+ /**
+ * @var User|string $user
+ */
+ $model = config('restify.auth.user_model');
+
+ $user = $model::forceCreate([
'name' => $request->input('name'),
'email' => $request->input('email'),
'password' => Hash::make($request->input('password')),
]);
- return data([
- 'user' => $user,
- 'token' => $user->createToken('login')
+ return rest($user)->indexMeta([
+ 'token' => $user->createToken('login')->plainTextToken,
]);
}
}
diff --git a/src/Commands/stubs/Auth/ResetPasswordController.stub b/src/Commands/stubs/Auth/ResetPasswordController.stub
index d4acdbbc..cd2f97ce 100644
--- a/src/Commands/stubs/Auth/ResetPasswordController.stub
+++ b/src/Commands/stubs/Auth/ResetPasswordController.stub
@@ -3,7 +3,6 @@
namespace {{namespace}};
use App\Models\User;
-use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Hash;
@@ -11,7 +10,7 @@ use Illuminate\Support\Facades\Password;
class ResetPasswordController extends Controller
{
- public function __invoke(Request $request): JsonResponse
+ public function __invoke(Request $request)
{
$request->validate([
'email' => 'required|email',
@@ -20,8 +19,7 @@ class ResetPasswordController extends Controller
]);
/** * @var User $user */
-
- $user = User::query()->where($request->only('email'))->firstOrFail();
+ $user = config('restify.auth.user_model')::query()->where($request->only('email'))->firstOrFail();
if (! Password::getRepository()->exists($user, $request->input('token'))) {
abort(400, 'Provided invalid token.');
@@ -32,6 +30,6 @@ class ResetPasswordController extends Controller
Password::deleteToken($user);
- return data("Password has been successfully changed");
+ return ok('Your password has been successfully reset.');
}
}
diff --git a/src/Commands/stubs/Auth/VerifyController.stub b/src/Commands/stubs/Auth/VerifyController.stub
index 62072ff8..8cbed357 100644
--- a/src/Commands/stubs/Auth/VerifyController.stub
+++ b/src/Commands/stubs/Auth/VerifyController.stub
@@ -13,9 +13,12 @@ class VerifyController extends Controller
{
public function __invoke(int $id, string $hash)
{
- $user = User::query()->findOrFail($id);
+ /**
+ * @var User $user
+ */
+ $user = config('restify.auth.user_model')::query()->findOrFail($id);
- if ($user instanceof Sanctumable && ! hash_equals((string)$hash, sha1($user->getEmailForVerification()))) {
+ if ($user instanceof Sanctumable && ! hash_equals((string) $hash, sha1($user->getEmailForVerification()))) {
throw new AuthorizationException('Invalid hash');
}
@@ -23,6 +26,6 @@ class VerifyController extends Controller
event(new Verified($user));
}
- return $user;
+ return rest($user);
}
}
diff --git a/src/Commands/stubs/Blades/reset-password.stub b/src/Commands/stubs/Blades/reset-password.stub
deleted file mode 100644
index c69cabfc..00000000
--- a/src/Commands/stubs/Blades/reset-password.stub
+++ /dev/null
@@ -1,14 +0,0 @@
-@component('mail::message')
-
-Hello!
-You are receiving this email because we received a password reset request for your account.
-
-@component('mail::button', ['url' => $url])
-
-This password reset link will expire in 60 minutes.
-
-If you did not request a password reset, no further action is required.
-
-@endcomponent
-@endcomponent
-
diff --git a/src/Commands/stubs/Email/ForgotPasswordMail.stub b/src/Commands/stubs/Email/ForgotPasswordMail.stub
deleted file mode 100644
index b5adaf7e..00000000
--- a/src/Commands/stubs/Email/ForgotPasswordMail.stub
+++ /dev/null
@@ -1,35 +0,0 @@
-markdown('restify.auth.reset-password')->with([
- 'url' => $this->url,
- ]);
- }
-}
diff --git a/src/Commands/stubs/Notifications/ForgotPasswordNotification.stub b/src/Commands/stubs/Notifications/ForgotPasswordNotification.stub
new file mode 100644
index 00000000..37179c5d
--- /dev/null
+++ b/src/Commands/stubs/Notifications/ForgotPasswordNotification.stub
@@ -0,0 +1,31 @@
+line('In order to reset your password, please click the button below.')
+ ->action('Reset password', $this->url)
+ ->line('If you did not request a password reset, please ignore this email.');
+ }
+}
diff --git a/src/Commands/stubs/Routes/forgotPasswordRoute.stub b/src/Commands/stubs/Routes/forgotPasswordRoute.stub
new file mode 100644
index 00000000..49d824bf
--- /dev/null
+++ b/src/Commands/stubs/Routes/forgotPasswordRoute.stub
@@ -0,0 +1,3 @@
+Route::post('forgotPassword', \App\Http\Controllers\Restify\Auth\ForgotPasswordController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.forgotPassword');
diff --git a/src/Commands/stubs/Routes/loginRoute.stub b/src/Commands/stubs/Routes/loginRoute.stub
new file mode 100644
index 00000000..b0e31c3b
--- /dev/null
+++ b/src/Commands/stubs/Routes/loginRoute.stub
@@ -0,0 +1,3 @@
+Route::post('login', \App\Http\Controllers\Restify\Auth\LoginController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.login');
diff --git a/src/Commands/stubs/Routes/registerRoute.stub b/src/Commands/stubs/Routes/registerRoute.stub
new file mode 100644
index 00000000..fb7695e7
--- /dev/null
+++ b/src/Commands/stubs/Routes/registerRoute.stub
@@ -0,0 +1,2 @@
+Route::post('register', \App\Http\Controllers\Restify\Auth\RegisterController::class)
+ ->name('restify.register');
diff --git a/src/Commands/stubs/Routes/resetPasswordRoute.stub b/src/Commands/stubs/Routes/resetPasswordRoute.stub
new file mode 100644
index 00000000..1f10235f
--- /dev/null
+++ b/src/Commands/stubs/Routes/resetPasswordRoute.stub
@@ -0,0 +1,3 @@
+Route::post('resetPassword', \App\Http\Controllers\Restify\Auth\ResetPasswordController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.resetPassword');
diff --git a/src/Commands/stubs/Routes/routes.stub b/src/Commands/stubs/Routes/routes.stub
deleted file mode 100644
index 68b0da3f..00000000
--- a/src/Commands/stubs/Routes/routes.stub
+++ /dev/null
@@ -1,19 +0,0 @@
-Route::post('register', \App\Http\Controllers\Restify\Auth\RegisterController::class)
- ->name('restify.register');
-
-Route::post('login', \App\Http\Controllers\Restify\Auth\LoginController::class)
- ->middleware('throttle:6,1')
- ->name('restify.login');
-
-Route::post('verify/{id}/{hash}', \App\Http\Controllers\Restify\Auth\VerifyController::class)
- ->middleware('throttle:6,1')
- ->name('restify.verify');
-
-Route::post('forgotPassword', \App\Http\Controllers\Restify\Auth\ForgotPasswordController::class)
- ->middleware('throttle:6,1')
- ->name('restify.forgotPassword');
-
-Route::post('resetPassword', \App\Http\Controllers\Restify\Auth\ResetPasswordController::class)
- ->middleware('throttle:6,1')
- ->name('restify.resetPassword');
-
diff --git a/src/Commands/stubs/Routes/verifyRoute.stub b/src/Commands/stubs/Routes/verifyRoute.stub
new file mode 100644
index 00000000..db98b1da
--- /dev/null
+++ b/src/Commands/stubs/Routes/verifyRoute.stub
@@ -0,0 +1,4 @@
+Route::post('verify/{id}/{hash}', \App\Http\Controllers\Restify\Auth\VerifyController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.verify');
+
diff --git a/src/Http/Controllers/Auth/ForgotPasswordController.php b/src/Http/Controllers/Auth/ForgotPasswordController.php
index 5c81c49e..0d0e3616 100644
--- a/src/Http/Controllers/Auth/ForgotPasswordController.php
+++ b/src/Http/Controllers/Auth/ForgotPasswordController.php
@@ -2,11 +2,11 @@
namespace Binaryk\LaravelRestify\Http\Controllers\Auth;
-use Binaryk\LaravelRestify\Mail\ForgotPasswordMail;
+use Binaryk\LaravelRestify\Notifications\ForgotPasswordNotification;
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
use Illuminate\Http\Request;
+use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Routing\Controller;
-use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
@@ -29,10 +29,8 @@ public function __invoke(Request $request)
$request->input('url') ?? config('restify.auth.password_reset_url')
);
- Mail::to($user->email)->send(
- new ForgotPasswordMail($url)
- );
+ (new AnonymousNotifiable())->route('mail', $user->email)->notify(new ForgotPasswordNotification($url));
- return data(__('Email sent.'));
+ return ok(__('Reset password link sent to your email.'));
}
}
diff --git a/src/Http/Controllers/Auth/LoginController.php b/src/Http/Controllers/Auth/LoginController.php
index 2b5adb84..26a1c6c1 100644
--- a/src/Http/Controllers/Auth/LoginController.php
+++ b/src/Http/Controllers/Auth/LoginController.php
@@ -3,14 +3,13 @@
namespace Binaryk\LaravelRestify\Http\Controllers\Auth;
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
-use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Hash;
class LoginController extends Controller
{
- public function __invoke(Request $request): JsonResponse
+ public function __invoke(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
@@ -28,9 +27,8 @@ public function __invoke(Request $request): JsonResponse
abort(401, 'Invalid credentials.');
}
- return data([
- 'user' => $user,
- 'token' => $user->createToken('login'),
+ return rest($user)->indexMeta([
+ 'token' => $user->createToken('login')->plainTextToken,
]);
}
}
diff --git a/src/Http/Controllers/Auth/RegisterController.php b/src/Http/Controllers/Auth/RegisterController.php
index 39619a6a..365b3197 100644
--- a/src/Http/Controllers/Auth/RegisterController.php
+++ b/src/Http/Controllers/Auth/RegisterController.php
@@ -2,7 +2,6 @@
namespace Binaryk\LaravelRestify\Http\Controllers\Auth;
-use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Config;
@@ -10,7 +9,7 @@
class RegisterController extends Controller
{
- public function __invoke(Request $request): JsonResponse
+ public function __invoke(Request $request)
{
$request->validate([
'email' => ['required', 'email', 'max:255', 'unique:'.Config::get('config.auth.table', 'users')],
@@ -25,9 +24,8 @@ public function __invoke(Request $request): JsonResponse
'password' => Hash::make($request->input('password')),
]);
- return data([
- 'user' => $user,
- 'token' => $user->createToken('login'),
+ return rest($user)->indexMeta([
+ 'token' => $user->createToken('login')->plainTextToken,
]);
}
}
diff --git a/src/Http/Controllers/Auth/ResetPasswordController.php b/src/Http/Controllers/Auth/ResetPasswordController.php
index 63883266..35da420d 100644
--- a/src/Http/Controllers/Auth/ResetPasswordController.php
+++ b/src/Http/Controllers/Auth/ResetPasswordController.php
@@ -3,7 +3,6 @@
namespace Binaryk\LaravelRestify\Http\Controllers\Auth;
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
-use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Hash;
@@ -11,7 +10,7 @@
class ResetPasswordController extends Controller
{
- public function __invoke(Request $request): JsonResponse
+ public function __invoke(Request $request)
{
$request->validate([
'email' => 'required|email',
@@ -20,7 +19,7 @@ public function __invoke(Request $request): JsonResponse
]);
/** * @var User $user */
- $user = config('config.auth.user_model')::query()->where($request->only('email'))->firstOrFail();
+ $user = config('restify.auth.user_model')::query()->where($request->only('email'))->firstOrFail();
if (! Password::getRepository()->exists($user, $request->input('token'))) {
abort(400, 'Provided invalid token.');
@@ -31,6 +30,6 @@ public function __invoke(Request $request): JsonResponse
Password::deleteToken($user);
- return data('Password has been successfully changed');
+ return ok('Your password has been successfully reset.');
}
}
diff --git a/src/Http/Controllers/Auth/VerifyController.php b/src/Http/Controllers/Auth/VerifyController.php
index eb8b1f93..8f657abd 100644
--- a/src/Http/Controllers/Auth/VerifyController.php
+++ b/src/Http/Controllers/Auth/VerifyController.php
@@ -26,6 +26,6 @@ public function __invoke(int $id, string $hash)
event(new Verified($user));
}
- return $user;
+ return rest($user);
}
}
diff --git a/src/LaravelRestifyServiceProvider.php b/src/LaravelRestifyServiceProvider.php
index 91842f06..f0606f43 100644
--- a/src/LaravelRestifyServiceProvider.php
+++ b/src/LaravelRestifyServiceProvider.php
@@ -27,7 +27,6 @@ public function configurePackage(Package $package): void
->hasConfigFile()
->hasMigration('create_action_logs_table')
->runsMigrations()
- ->hasViews('restify')
->hasCommands([
RepositoryCommand::class,
ActionCommand::class,
diff --git a/src/Mail/ForgotPasswordMail.php b/src/Mail/ForgotPasswordMail.php
deleted file mode 100644
index 424be455..00000000
--- a/src/Mail/ForgotPasswordMail.php
+++ /dev/null
@@ -1,35 +0,0 @@
-view('views.emails.reset-password')->with([
- 'url' => $this->url,
- ]);
- }
-}
diff --git a/src/Notifications/ForgotPasswordNotification.php b/src/Notifications/ForgotPasswordNotification.php
new file mode 100644
index 00000000..baaf18f9
--- /dev/null
+++ b/src/Notifications/ForgotPasswordNotification.php
@@ -0,0 +1,31 @@
+line('In order to reset your password, please click the button below.')
+ ->action('Reset password', $this->url)
+ ->line('If you did not request a password reset, please ignore this email.');
+ }
+}
diff --git a/src/Resources/views/emails/reset-password.blade.php b/src/Resources/views/emails/reset-password.blade.php
deleted file mode 100644
index 281a00f3..00000000
--- a/src/Resources/views/emails/reset-password.blade.php
+++ /dev/null
@@ -1,15 +0,0 @@
-@component('mail::message')
-
-Hello!
-You are receiving this email because we received a password reset request for your account.
-
-@component('mail::button', ['url' => $url])
-Reset now
-@endcomponent
-
-This password reset link will expire in 60 minutes.
-
-If you did not request a password reset, no further action is required.
-
-@endcomponent
-
diff --git a/src/RestifyApplicationServiceProvider.php b/src/RestifyApplicationServiceProvider.php
index 3969e268..4531befb 100644
--- a/src/RestifyApplicationServiceProvider.php
+++ b/src/RestifyApplicationServiceProvider.php
@@ -78,29 +78,39 @@ protected function gate(): void
protected function authRoutes(): void
{
- Route::macro('restifyAuth', function ($prefix = '/') {
+ Route::macro('restifyAuth', function ($prefix = '/', array $actions = ['register', 'login', 'verifyEmail', 'forgotPassword', 'resetPassword']) {
Route::group([
'prefix' => $prefix,
'middleware' => ['api'],
- ], function () {
- Route::post('register', RegisterController::class)
- ->name('restify.register');
-
- Route::post('login', LoginController::class)
- ->middleware('throttle:6,1')
- ->name('restify.login');
-
- Route::post('verify/{id}/{hash}', VerifyController::class)
- ->middleware('throttle:6,1')
- ->name('restify.verify');
-
- Route::post('forgotPassword', ForgotPasswordController::class)
- ->middleware('throttle:6,1')
- ->name('restify.forgotPassword');
-
- Route::post('resetPassword', ResetPasswordController::class)
- ->middleware('throttle:6,1')
- ->name('restify.resetPassword');
+ ], function () use ($actions) {
+ if (in_array('register', $actions, true)) {
+ Route::post('register', RegisterController::class)
+ ->name('restify.register');
+ }
+
+ if (in_array('login', $actions, true)) {
+ Route::post('login', LoginController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.login');
+ }
+
+ if (in_array('verifyEmail', $actions, true)) {
+ Route::post('verify/{id}/{hash}', VerifyController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.verify');
+ }
+
+ if (in_array('forgotPassword', $actions, true)) {
+ Route::post('forgotPassword', ForgotPasswordController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.forgotPassword');
+ }
+
+ if (in_array('resetPassword', $actions, true)) {
+ Route::post('resetPassword', ResetPasswordController::class)
+ ->middleware('throttle:6,1')
+ ->name('restify.resetPassword');
+ }
});
});
}
diff --git a/tests/Actions/ListActionsControllerTest.php b/tests/Actions/ListActionsControllerTest.php
index 0fd75036..7cdd077f 100644
--- a/tests/Actions/ListActionsControllerTest.php
+++ b/tests/Actions/ListActionsControllerTest.php
@@ -6,23 +6,8 @@
use Binaryk\LaravelRestify\Tests\IntegrationTestCase;
use Illuminate\Testing\Fluent\AssertableJson;
-// TODO: Please refactor all tests using assertJson (as the first test does).
class ListActionsControllerTest extends IntegrationTestCase
{
- public function test_could_list_actions_for_repository(): void
- {
- $_SERVER['actions.posts.invalidate'] = false;
-
- $this->getJson(PostRepository::route('actions'))
- ->assertOk()
- ->assertJson(
- fn (AssertableJson $json) => $json
- ->count('data', 1)
- ->where('data.0.uriKey', 'publish-post-action')
- ->etc()
- );
- }
-
public function test_could_list_actions_for_given_repository(): void
{
$this->mockPosts(1, 2);
@@ -32,15 +17,13 @@ public function test_could_list_actions_for_given_repository(): void
$this->getJson(PostRepository::route('1/actions'))
->assertSuccessful()
- ->assertJsonCount(2, 'data')
- ->assertJsonStructure([
- 'data' => [
- [
- 'name',
- 'uriKey',
- ],
- ],
- ]);
+ ->assertJson(
+ fn (AssertableJson $json) => $json
+ ->count('data', 2)
+ ->where('data.0.uriKey', 'publish-post-action')
+ ->where('data.1.uriKey', 'invalidate-post-action')
+ ->etc()
+ );
}
public function test_can_list_actions_only_for_show(): void
@@ -50,26 +33,43 @@ public function test_can_list_actions_only_for_show(): void
$_SERVER['actions.posts.onlyOnShow'] = true;
$_SERVER['actions.posts.publish.onlyOnShow'] = false;
- $response = $this->getJson(PostRepository::route('1/actions'))
- ->assertJsonCount(1, 'data');
-
- $this->assertEquals('invalidate-post-action', $response->json('data.0.uriKey'));
-
- $response = $this->getJson(PostRepository::route('actions'))
- ->assertJsonCount(1, 'data');
+ $this->getJson(PostRepository::route('1/actions'))
+ ->assertSuccessful()
+ ->assertJson(
+ fn (AssertableJson $json) => $json
+ ->count('data', 1)
+ ->where('data.0.uriKey', 'invalidate-post-action')
+ ->etc()
+ );
- $this->assertEquals('publish-post-action', $response->json('data.0.uriKey'));
+ $this->getJson(PostRepository::route('actions'))
+ ->assertSuccessful()
+ ->assertJson(
+ fn (AssertableJson $json) => $json
+ ->count('data', 1)
+ ->where('data.0.uriKey', 'publish-post-action')
+ ->etc()
+ );
$_SERVER['actions.posts.onlyOnShow'] = false;
$_SERVER['actions.posts.publish.onlyOnShow'] = false;
$this->getJson(PostRepository::route('1/actions'))
- ->assertJsonCount(0, 'data');
-
- $response = $this->getJson(PostRepository::route('actions'))
- ->assertJsonCount(2, 'data');
+ ->assertSuccessful()
+ ->assertJson(
+ fn (AssertableJson $json) => $json
+ ->count('data', 0)
+ ->etc()
+ );
- $this->assertEquals('publish-post-action', $response->json('data.0.uriKey'));
- $this->assertEquals('invalidate-post-action', $response->json('data.1.uriKey'));
+ $this->getJson(PostRepository::route('actions'))
+ ->assertSuccessful()
+ ->assertJson(
+ fn (AssertableJson $json) => $json
+ ->count('data', 2)
+ ->where('data.0.uriKey', 'publish-post-action')
+ ->where('data.1.uriKey', 'invalidate-post-action')
+ ->etc()
+ );
}
}