From a2b3269362d27e1ac87f8f5b269fcad8c5714e42 Mon Sep 17 00:00:00 2001 From: Sergio Ojeda Date: Wed, 15 Nov 2023 16:32:42 -0300 Subject: [PATCH] Feature/endpoint example (#11) * feat: users endpoint to boirlerplate * feat: Add spatie/laravel-query-builder dependency --- .env.example | 8 +- composer.json | 3 +- composer.lock | 136 ++++++- config/ide-helper.php | 343 ++++++++++++++++++ routes/api.php | 18 + .../Controllers/DeleteUserController.php | 20 + .../Users/Controllers/GetUserController.php | 19 + .../Users/Controllers/ListUserController.php | 24 ++ .../Users/Controllers/StoreUserController.php | 22 ++ src/App/Users/Request/StoreUserRequest.php | 34 ++ .../Users/Transformers/UserTransformer.php | 20 + src/Domain/Users/Actions/StoreUserAction.php | 25 ++ .../Users/DataTransferObjects/UserDto.php | 30 ++ src/Domain/Users/Models/User.php | 30 ++ 14 files changed, 726 insertions(+), 6 deletions(-) create mode 100644 config/ide-helper.php create mode 100644 src/App/Users/Controllers/DeleteUserController.php create mode 100644 src/App/Users/Controllers/GetUserController.php create mode 100644 src/App/Users/Controllers/ListUserController.php create mode 100644 src/App/Users/Controllers/StoreUserController.php create mode 100644 src/App/Users/Request/StoreUserRequest.php create mode 100644 src/App/Users/Transformers/UserTransformer.php create mode 100644 src/Domain/Users/Actions/StoreUserAction.php create mode 100644 src/Domain/Users/DataTransferObjects/UserDto.php diff --git a/.env.example b/.env.example index 61b5b3a20ba..e84397609f0 100644 --- a/.env.example +++ b/.env.example @@ -9,11 +9,11 @@ LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug DB_CONNECTION=mysql -DB_HOST=127.0.0.1 +DB_HOST=mysql DB_PORT=3306 DB_DATABASE=laravel -DB_USERNAME=root -DB_PASSWORD= +DB_USERNAME=sail +DB_PASSWORD=password BROADCAST_DRIVER=log CACHE_DRIVER=file @@ -61,4 +61,4 @@ VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" VITE_PUSHER_HOST="${PUSHER_HOST}" VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" -VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" \ No newline at end of file +VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/composer.json b/composer.json index d88105a53a3..932edad2b3c 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", "laravel/sanctum": "^3.3", - "laravel/tinker": "^2.8" + "laravel/tinker": "^2.8", + "spatie/laravel-query-builder": "^5.6" }, "require-dev": { "akrillia/laravel-beyond": "^6.0", diff --git a/composer.lock b/composer.lock index ab02d3fac12..eeb7cc941c8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "49b349d25073cd95596ab87f208b88ff", + "content-hash": "44f11c8f581a998d8425ccad1f2f8c89", "packages": [ { "name": "brick/math", @@ -3251,6 +3251,140 @@ ], "time": "2023-04-15T23:01:58+00:00" }, + { + "name": "spatie/laravel-package-tools", + "version": "1.16.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "cc7c991555a37f9fa6b814aa03af73f88026a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/cc7c991555a37f9fa6b814aa03af73f88026a83d", + "reference": "cc7c991555a37f9fa6b814aa03af73f88026a83d", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.28|^10.0", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7|^8.0", + "pestphp/pest": "^1.22", + "phpunit/phpunit": "^9.5.24", + "spatie/pest-plugin-test-time": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-08-23T09:04:39+00:00" + }, + { + "name": "spatie/laravel-query-builder", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-query-builder.git", + "reference": "a91a348f2d205026ac726850c969e4da15061aa9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/a91a348f2d205026ac726850c969e4da15061aa9", + "reference": "a91a348f2d205026ac726850c969e4da15061aa9", + "shasum": "" + }, + "require": { + "illuminate/database": "^9.0|^10.0", + "illuminate/http": "^9.0|^10.0", + "illuminate/support": "^9.0|^10.0", + "php": "^8.0", + "spatie/laravel-package-tools": "^1.11" + }, + "require-dev": { + "ext-json": "*", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^7.0|^8.0", + "pestphp/pest": "^1.20", + "phpunit/phpunit": "^9.6.11", + "spatie/invade": "^2.0", + "spatie/laravel-ray": "^1.28" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\QueryBuilder\\QueryBuilderServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\QueryBuilder\\": "src", + "Spatie\\QueryBuilder\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily build Eloquent queries from API requests", + "homepage": "https://github.com/spatie/laravel-query-builder", + "keywords": [ + "laravel-query-builder", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-query-builder/issues", + "source": "https://github.com/spatie/laravel-query-builder" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2023-10-05T10:21:47+00:00" + }, { "name": "symfony/console", "version": "v6.3.4", diff --git a/config/ide-helper.php b/config/ide-helper.php new file mode 100644 index 00000000000..c3b951f6070 --- /dev/null +++ b/config/ide-helper.php @@ -0,0 +1,343 @@ + '_ide_helper.php', + + /* + |-------------------------------------------------------------------------- + | Models filename + |-------------------------------------------------------------------------- + | + | The default filename for the models helper file + | + */ + + 'models_filename' => '_ide_helper_models.php', + + /* + |-------------------------------------------------------------------------- + | Where to write the PhpStorm specific meta file + |-------------------------------------------------------------------------- + | + | PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary + | files in it, should you need additional files for your project; e.g. + | `.phpstorm.meta.php/laravel_ide_Helper.php'. + | + */ + 'meta_filename' => '.phpstorm.meta.php', + + /* + |-------------------------------------------------------------------------- + | Fluent helpers + |-------------------------------------------------------------------------- + | + | Set to true to generate commonly used Fluent methods + | + */ + + 'include_fluent' => false, + + /* + |-------------------------------------------------------------------------- + | Factory Builders + |-------------------------------------------------------------------------- + | + | Set to true to generate factory generators for better factory() + | method auto-completion. + | + | Deprecated for Laravel 8 or latest. + | + */ + + 'include_factory_builders' => false, + + /* + |-------------------------------------------------------------------------- + | Write Model Magic methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write magic methods of model + | + */ + + 'write_model_magic_where' => true, + + /* + |-------------------------------------------------------------------------- + | Write Model External Eloquent Builder methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write external eloquent builder methods + | + */ + + 'write_model_external_builder_methods' => true, + + /* + |-------------------------------------------------------------------------- + | Write Model relation count properties + |-------------------------------------------------------------------------- + | + | Set to false to disable writing of relation count properties to model DocBlocks. + | + */ + + 'write_model_relation_count_properties' => true, + + /* + |-------------------------------------------------------------------------- + | Write Eloquent Model Mixins + |-------------------------------------------------------------------------- + | + | This will add the necessary DocBlock mixins to the model class + | contained in the Laravel Framework. This helps the IDE with + | auto-completion. + | + | Please be aware that this setting changes a file within the /vendor directory. + | + */ + + 'write_eloquent_model_mixins' => false, + + /* + |-------------------------------------------------------------------------- + | Helper files to include + |-------------------------------------------------------------------------- + | + | Include helper files. By default not included, but can be toggled with the + | -- helpers (-H) option. Extra helper files can be included. + | + */ + + 'include_helpers' => false, + + 'helper_files' => [ + base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', + ], + + /* + |-------------------------------------------------------------------------- + | Model locations to include + |-------------------------------------------------------------------------- + | + | Define in which directories the ide-helper:models command should look + | for models. + | + | glob patterns are supported to easier reach models in sub-directories, + | e.g. `app/Services/* /Models` (without the space) + | + */ + + 'model_locations' => [ + 'src', + ], + + /* + |-------------------------------------------------------------------------- + | Models to ignore + |-------------------------------------------------------------------------- + | + | Define which models should be ignored. + | + */ + + 'ignored_models' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Models hooks + |-------------------------------------------------------------------------- + | + | Define which hook classes you want to run for models to add custom information + | + | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. + | + */ + + 'model_hooks' => [ + // App\Support\IdeHelper\MyModelHook::class + ], + + /* + |-------------------------------------------------------------------------- + | Extra classes + |-------------------------------------------------------------------------- + | + | These implementations are not really extended, but called with magic functions + | + */ + + 'extra' => [ + 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], + 'Session' => ['Illuminate\Session\Store'], + ], + + 'magic' => [], + + /* + |-------------------------------------------------------------------------- + | Interface implementations + |-------------------------------------------------------------------------- + | + | These interfaces will be replaced with the implementing class. Some interfaces + | are detected by the helpers, others can be listed below. + | + */ + + 'interfaces' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Support for custom DB types + |-------------------------------------------------------------------------- + | + | This setting allow you to map any custom database type (that you may have + | created using CREATE TYPE statement or imported using database plugin + | / extension to a Doctrine type. + | + | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are: + | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql' + | + | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant + | + | The value of the array is an array of type mappings. Key is the name of the custom type, + | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in + | our case it is 'json_array'. Doctrine types are listed here: + | https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#types + | + | So to support jsonb in your models when working with Postgres, just add the following entry to the array below: + | + | "postgresql" => array( + | "jsonb" => "json_array", + | ), + | + */ + 'custom_db_types' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | Support for camel cased models + |-------------------------------------------------------------------------- + | + | There are some Laravel packages (such as Eloquence) that allow for accessing + | Eloquent model properties via camel case, instead of snake case. + | + | Enabling this option will support these packages by saving all model + | properties as camel case, instead of snake case. + | + | For example, normally you would see this: + | + | * @property \Illuminate\Support\Carbon $created_at + | * @property \Illuminate\Support\Carbon $updated_at + | + | With this enabled, the properties will be this: + | + | * @property \Illuminate\Support\Carbon $createdAt + | * @property \Illuminate\Support\Carbon $updatedAt + | + | Note, it is currently an all-or-nothing option. + | + */ + 'model_camel_case_properties' => false, + + /* + |-------------------------------------------------------------------------- + | Property Casts + |-------------------------------------------------------------------------- + | + | Cast the given "real type" to the given "type". + | + */ + 'type_overrides' => [ + 'integer' => 'int', + 'boolean' => 'bool', + ], + + /* + |-------------------------------------------------------------------------- + | Include DocBlocks from classes + |-------------------------------------------------------------------------- + | + | Include DocBlocks from classes to allow additional code inspection for + | magic methods and properties. + | + */ + 'include_class_docblocks' => false, + + /* + |-------------------------------------------------------------------------- + | Force FQN usage + |-------------------------------------------------------------------------- + | + | Use the fully qualified (class) name in docBlock, + | event if class exists in a given file + | or there is an import (use className) of a given class + | + */ + 'force_fqn' => false, + + /* + |-------------------------------------------------------------------------- + | Use generics syntax + |-------------------------------------------------------------------------- + | + | Use generics syntax within DocBlocks, + | e.g. `Collection` instead of `Collection|User[]`. + | + */ + 'use_generics_annotations' => true, + + /* + |-------------------------------------------------------------------------- + | Additional relation types + |-------------------------------------------------------------------------- + | + | Sometimes it's needed to create custom relation types. The key of the array + | is the Relationship Method name. The value of the array is the canonical class + | name of the Relationship, e.g. `'relationName' => RelationShipClass::class`. + | + */ + 'additional_relation_types' => [], + + /* + |-------------------------------------------------------------------------- + | Additional relation return types + |-------------------------------------------------------------------------- + | + | When using custom relation types its possible for the class name to not contain + | the proper return type of the relation. The key of the array is the relationship + | method name. The value of the array is the return type of the relation. + | e.g. `'relationName' => 'many'`. + | + */ + 'additional_relation_return_types' => [], + + /* + |-------------------------------------------------------------------------- + | Run artisan commands after migrations to generate model helpers + |-------------------------------------------------------------------------- + | + | The specified commands should run after migrations are finished running. + | + */ + 'post_migrate' => [ + 'ide-helper:models --write', + ], + +]; diff --git a/routes/api.php b/routes/api.php index 889937e11a5..7d5f73ce56a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,9 @@ get('/user', function (Request $request) { return $request->user(); }); + +/* +|-------------------------------------------------------------------------- +| Users Routes +|-------------------------------------------------------------------------- +*/ +Route::prefix('users') + ->middleware([]) + ->group(static function () { + Route::get('/', ListUserController::class); + Route::get('/{user}', GetUserController::class); + Route::post('/', StoreUserController::class); + Route::delete('/{user}', DeleteUserController::class); + }); diff --git a/src/App/Users/Controllers/DeleteUserController.php b/src/App/Users/Controllers/DeleteUserController.php new file mode 100644 index 00000000000..d35aee7e1d6 --- /dev/null +++ b/src/App/Users/Controllers/DeleteUserController.php @@ -0,0 +1,20 @@ +delete(); + + return responder() + ->success() + ->respond(); + } +} diff --git a/src/App/Users/Controllers/GetUserController.php b/src/App/Users/Controllers/GetUserController.php new file mode 100644 index 00000000000..5a0ae3e4248 --- /dev/null +++ b/src/App/Users/Controllers/GetUserController.php @@ -0,0 +1,19 @@ +success($user, UserTransformer::class) + ->respond(); + } +} diff --git a/src/App/Users/Controllers/ListUserController.php b/src/App/Users/Controllers/ListUserController.php new file mode 100644 index 00000000000..1affdccb48a --- /dev/null +++ b/src/App/Users/Controllers/ListUserController.php @@ -0,0 +1,24 @@ +get(); + + return responder() + ->success($users, UserTransformer::class) + ->respond(); + } +} diff --git a/src/App/Users/Controllers/StoreUserController.php b/src/App/Users/Controllers/StoreUserController.php new file mode 100644 index 00000000000..357b229b6c1 --- /dev/null +++ b/src/App/Users/Controllers/StoreUserController.php @@ -0,0 +1,22 @@ +execute($request->toDto()); + + return responder() + ->success($user, UserTransformer::class) + ->respond(); + } +} diff --git a/src/App/Users/Request/StoreUserRequest.php b/src/App/Users/Request/StoreUserRequest.php new file mode 100644 index 00000000000..bd614d19b3a --- /dev/null +++ b/src/App/Users/Request/StoreUserRequest.php @@ -0,0 +1,34 @@ + ['required'], + self::EMAIL => ['required', 'email:strict'], + self::PASSWORD => ['required', 'confirmed', Password::min(8)->letters()->numbers()], + ]; + } + + public function toDto(): UserDto + { + return new UserDto( + name: $this->string(self::NAME)->toString(), + email: $this->string(self::EMAIL)->toString(), + password: $this->string(self::PASSWORD)->toString(), + ); + } +} diff --git a/src/App/Users/Transformers/UserTransformer.php b/src/App/Users/Transformers/UserTransformer.php new file mode 100644 index 00000000000..56959a3ed35 --- /dev/null +++ b/src/App/Users/Transformers/UserTransformer.php @@ -0,0 +1,20 @@ + (int) $user->id, + 'name' => (string) $user->name, + 'email' => (string) $user->email + ]; + } +} diff --git a/src/Domain/Users/Actions/StoreUserAction.php b/src/Domain/Users/Actions/StoreUserAction.php new file mode 100644 index 00000000000..a1d0bfed543 --- /dev/null +++ b/src/Domain/Users/Actions/StoreUserAction.php @@ -0,0 +1,25 @@ + $userDto->getName(), + 'email' => $userDto->getEmail(), + 'password' => $this->hasher->make($userDto->getPassword()), + ]); + } +} diff --git a/src/Domain/Users/DataTransferObjects/UserDto.php b/src/Domain/Users/DataTransferObjects/UserDto.php new file mode 100644 index 00000000000..88e2af78d2a --- /dev/null +++ b/src/Domain/Users/DataTransferObjects/UserDto.php @@ -0,0 +1,30 @@ +name; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getPassword(): string + { + return $this->password; + } +} diff --git a/src/Domain/Users/Models/User.php b/src/Domain/Users/Models/User.php index 4511961b0f6..67fe9bec14b 100644 --- a/src/Domain/Users/Models/User.php +++ b/src/Domain/Users/Models/User.php @@ -9,6 +9,36 @@ use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; +/** + * Domain\Users\Models\User + * + * @property int $id + * @property string $name + * @property string $email + * @property \Illuminate\Support\Carbon|null $email_verified_at + * @property mixed $password + * @property string|null $remember_token + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read int|null $notifications_count + * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read int|null $tokens_count + * + * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User query() + * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerifiedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) + * + * @mixin \Eloquent + */ class User extends Authenticatable { use HasApiTokens;