From 508d2118c004f2f5decb923985339456cefc5065 Mon Sep 17 00:00:00 2001 From: Mohamed Shehata Date: Sat, 12 Feb 2022 01:05:59 +0200 Subject: [PATCH 1/5] register user with appropriate validation --- app/Abstracts/AbstractActionService.php | 37 ++ app/Abstracts/AbstractMySQLRepository.php | 72 +++ app/Contracts/RepositoryInterface.php | 48 ++ app/Exceptions/Handler.php | 2 +- app/Http/Controllers/UserController.php | 52 +++ app/Http/Requests/StoreUserRequest.php | 34 ++ app/Repositories/UserRepository.php | 18 + app/Rules/PostCode.php | 47 ++ app/Services/UserCountingService.php | 28 ++ app/Services/UserSavingService.php | 28 ++ app/User.php | 3 +- composer.json | 1 + composer.lock | 529 +++++++++++++++++++++- config/services.php | 3 + resources/lang/en/messages.php | 8 + resources/lang/en/validation.php | 2 + resources/views/layouts/app.blade.php | 45 ++ resources/views/users/create.blade.php | 103 +++++ routes/web.php | 7 + 19 files changed, 1063 insertions(+), 4 deletions(-) create mode 100644 app/Abstracts/AbstractActionService.php create mode 100644 app/Abstracts/AbstractMySQLRepository.php create mode 100644 app/Contracts/RepositoryInterface.php create mode 100644 app/Http/Controllers/UserController.php create mode 100644 app/Http/Requests/StoreUserRequest.php create mode 100644 app/Repositories/UserRepository.php create mode 100644 app/Rules/PostCode.php create mode 100644 app/Services/UserCountingService.php create mode 100644 app/Services/UserSavingService.php create mode 100644 resources/lang/en/messages.php create mode 100644 resources/views/layouts/app.blade.php create mode 100644 resources/views/users/create.blade.php diff --git a/app/Abstracts/AbstractActionService.php b/app/Abstracts/AbstractActionService.php new file mode 100644 index 0000000..a600410 --- /dev/null +++ b/app/Abstracts/AbstractActionService.php @@ -0,0 +1,37 @@ + + */ +abstract class AbstractActionService +{ + + /** + * + * @var RepositoryInterface + */ + protected $repository; + + /** + * Create new instance + * + * @param RepositoryInterface $repository + */ + public function __construct(RepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Performs the service action + * + * @param array $data The data used in executing the service action. + */ + abstract public function execute($data = []); +} diff --git a/app/Abstracts/AbstractMySQLRepository.php b/app/Abstracts/AbstractMySQLRepository.php new file mode 100644 index 0000000..132c251 --- /dev/null +++ b/app/Abstracts/AbstractMySQLRepository.php @@ -0,0 +1,72 @@ + + */ +abstract class AbstractMySQLRepository implements RepositoryInterface +{ + + /** + * @var Model The model which query will be run against. + */ + protected $model; + + /** + * @param Model $model + */ + public function __construct(Model $model) + { + $this->model = $model; + } + + /** + * @inheritDoc + */ + public function save(array $attributes): Model + { + return $this->model->create($attributes); + } + + /** + * @inheritDoc + */ + public function count(): int + { + return $this->model->count(); + } + + /** + * @inheritDoc + */ + public function latest(): self + { + $this->model = $this->model->latest(); + + return $this; + } + + /** + * @inheritDoc + */ + public function limit(int $limit): self + { + $this->model = $this->model->limit($limit); + + return $this; + } + + /** + * @inheritDoc + */ + public function get(array $columns = ['*']): \Illuminate\Database\Eloquent\Collection + { + return $this->model->get($columns); + } +} diff --git a/app/Contracts/RepositoryInterface.php b/app/Contracts/RepositoryInterface.php new file mode 100644 index 0000000..f308a30 --- /dev/null +++ b/app/Contracts/RepositoryInterface.php @@ -0,0 +1,48 @@ + + */ +interface RepositoryInterface +{ + /** + * Store model in database and return the instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(array $attributes): \Illuminate\Database\Eloquent\Model; + + /** + * Count existed models in database + * + * @return int + */ + public function count(): int; + + /** + * Alias for order by `created_at` + * + * @return self + */ + public function latest(): self; + + /** + * Limit query result count + * + * @return self + */ + public function limit(int $limit): self; + + /** + * Get query results + * + * @param array $columns Columns to select from database + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get(array $columns = ['*']): \Illuminate\Database\Eloquent\Collection; + +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 7fb0864..7e463a4 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -48,7 +48,7 @@ public function report(Throwable $exception) * @return Response * @throws Throwable */ - public function render($request, Throwable $exception): Response + public function render($request, Throwable $exception) { return parent::render($request, $exception); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..f87f66b --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,52 @@ + + */ +class UserController extends Controller +{ + /** + * Display Form for creating new user. + * + * @return \Illuminate\Contracts\View\View + */ + public function create(\App\Services\UserCountingService $countingService) + { + $userCount = $countingService->execute(); + return View::make('users.create', compact('userCount')); + } + + /** + * Store new user in storage system. + * + * @param StoreUserRequest $request + * @param UserSavingService $savingService + * + * @return \Illuminate\Http\RedirectResponse + */ + public function store( + StoreUserRequest $request, + UserSavingService $savingService + ) + { + try { + $savingService->execute($request->validated()); + return redirect()->back()->with('success', __('messages.user.create_success')); + } catch (Exception $exc) { + Log::alert('Failed to store user', [ + 'error_message' => $exc->getMessage() + ]); + } + return redirect()->back()->with('error', __('messages.user.create_failed')); + } +} diff --git a/app/Http/Requests/StoreUserRequest.php b/app/Http/Requests/StoreUserRequest.php new file mode 100644 index 0000000..114ba8b --- /dev/null +++ b/app/Http/Requests/StoreUserRequest.php @@ -0,0 +1,34 @@ + ['required', 'string', 'max:255'], + 'postcode' => ['bail', 'required', 'string', 'max:100', new PostCode()], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:6', 'confirmed'], + ]; + } +} diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php new file mode 100644 index 0000000..babb36a --- /dev/null +++ b/app/Repositories/UserRepository.php @@ -0,0 +1,18 @@ + + */ +class UserRepository extends AbstractMySQLRepository +{ + public function __construct(User $model) + { + parent::__construct($model); + } +} diff --git a/app/Rules/PostCode.php b/app/Rules/PostCode.php new file mode 100644 index 0000000..e4a9ac5 --- /dev/null +++ b/app/Rules/PostCode.php @@ -0,0 +1,47 @@ +ok()) { + return $response->json()['result']; + } + } catch (\Exception $exc) { + Log::alert("Failed to validate post code.", [ + 'validation_url' => $url, + 'error_message' => $exc->getMessage() + ]); + } + + return false; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return __('validation.postcode'); + } +} diff --git a/app/Services/UserCountingService.php b/app/Services/UserCountingService.php new file mode 100644 index 0000000..5341c5e --- /dev/null +++ b/app/Services/UserCountingService.php @@ -0,0 +1,28 @@ + + */ +class UserCountingService extends AbstractActionService +{ + + public function __construct(UserRepository $repository) + { + parent::__construct($repository); + } + + /** + * @inheritDoc + */ + public function execute($data = []) + { + return $this->repository->count(); + } +} diff --git a/app/Services/UserSavingService.php b/app/Services/UserSavingService.php new file mode 100644 index 0000000..3aac952 --- /dev/null +++ b/app/Services/UserSavingService.php @@ -0,0 +1,28 @@ + + */ +class UserSavingService extends AbstractActionService +{ + + public function __construct(UserRepository $repository) + { + parent::__construct($repository); + } + + public function execute($userData = []) + { + $userData['password'] = Hash::make($userData['password']); + + return $this->repository->save($userData); + } +} diff --git a/app/User.php b/app/User.php index e79dab7..f2805c8 100644 --- a/app/User.php +++ b/app/User.php @@ -2,7 +2,6 @@ namespace App; -use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -16,7 +15,7 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'name', 'email', 'password', + 'name', 'email', 'postcode', 'password', ]; /** diff --git a/composer.json b/composer.json index 7eec416..b634c80 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "require": { "php": "^7.2", "fideloper/proxy": "^4.0", + "guzzlehttp/guzzle": "^7.4", "laravel/framework": "^7.0", "laravel/tinker": "^2.0" }, diff --git a/composer.lock b/composer.lock index 6e92ade..509b61b 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": "6e58de05a7bfbbbf003da7e9ed4d75dd", + "content-hash": "22090ab338d196d8dd6651618e89791e", "packages": [ { "name": "brick/math", @@ -427,6 +427,329 @@ }, "time": "2020-10-22T13:48:01+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2021-12-06T18:43:05+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2021-10-06T17:43:30+00:00" + }, { "name": "laravel/framework", "version": "v7.30.4", @@ -1389,6 +1712,166 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/log", "version": "1.1.4", @@ -1565,6 +2048,50 @@ }, "time": "2021-10-10T13:37:39+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "ramsey/collection", "version": "1.2.2", diff --git a/config/services.php b/config/services.php index 2a1d616..4bfde62 100644 --- a/config/services.php +++ b/config/services.php @@ -30,4 +30,7 @@ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], + 'postcode' =>[ + 'validate_url' => env('POSTCODE_VALIDATE_URL', 'http://api.postcodes.io/postcodes/:postcode/validate'), + ] ]; diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php new file mode 100644 index 0000000..60643b5 --- /dev/null +++ b/resources/lang/en/messages.php @@ -0,0 +1,8 @@ +[ + 'create_success' => 'User created successfully.', + 'create_failed' => 'Failed to create user.' + ], +]; \ No newline at end of file diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index ce1d80d..a8f8063 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -118,6 +118,8 @@ 'url' => 'The :attribute format is invalid.', 'uuid' => 'The :attribute must be a valid UUID.', + 'postcode' => 'The :attribute is invalid.', + /* |-------------------------------------------------------------------------- | Custom Validation Language Lines diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 0000000..0c4b8e4 --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,45 @@ + + + + + + + + + + {{ config('app.name', "I'm Property") }} + + + + + + + + + +
+ + +
+ @yield('content') +
+
+ + \ No newline at end of file diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php new file mode 100644 index 0000000..acf274a --- /dev/null +++ b/resources/views/users/create.blade.php @@ -0,0 +1,103 @@ +@extends('layouts.app') + +@section('content') +
+ @if(session('success')) + + @endif + @if(session('error')) + + @endif +
+
+
+
+ {{ __('Register') }} + Currently ({{$userCount}}) registered users +
+ +
+
+ @csrf + +
+ + +
+ + + @error('name') + + {{ $message }} + + @enderror +
+
+
+ + +
+ + + @error('postcode') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 1a673a1..8967401 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,7 @@ name('users.')->group(function($router){ + $router->get('create', [UserController::class, 'create'])->name('create'); + $router->post('/', [UserController::class, 'store'])->name('store'); +}); \ No newline at end of file From 27fcebff266f6ed2fe33373544cbdcb2e60da035 Mon Sep 17 00:00:00 2001 From: Mohamed Shehata Date: Sat, 12 Feb 2022 01:06:24 +0200 Subject: [PATCH 2/5] send welcome email after registeration --- app/Notifications/WelcomeNewUserEmail.php | 38 +++++++++++++++++++++++ app/User.php | 15 +++++++++ 2 files changed, 53 insertions(+) create mode 100644 app/Notifications/WelcomeNewUserEmail.php diff --git a/app/Notifications/WelcomeNewUserEmail.php b/app/Notifications/WelcomeNewUserEmail.php new file mode 100644 index 0000000..f687f3f --- /dev/null +++ b/app/Notifications/WelcomeNewUserEmail.php @@ -0,0 +1,38 @@ +line('Dear ' . $notifiable->name . ',') + ->line('Welcome to '. config('app.name')) + ->action('Visit us', url('/')) + ->line('Thank you for using our application!'); + } +} diff --git a/app/User.php b/app/User.php index f2805c8..91237f4 100644 --- a/app/User.php +++ b/app/User.php @@ -2,6 +2,7 @@ namespace App; +use App\Notifications\WelcomeNewUserEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -35,4 +36,18 @@ class User extends Authenticatable protected $casts = [ 'email_verified_at' => 'datetime', ]; + + + /** + * The "booted" method of the model. + * + * @return void + */ + protected static function booted() + { + // Register model created event + static::created(function (User $user) { + $user->notify(new WelcomeNewUserEmail()); + }); + } } From 43aaa24777a676b6781fc208b90aaa02a6abdb4b Mon Sep 17 00:00:00 2001 From: Mohamed Shehata Date: Sat, 12 Feb 2022 01:07:01 +0200 Subject: [PATCH 3/5] list recently registered users artisan command --- .../Commands/ListRecentlyRegisteredUsers.php | 43 +++++++++++++++++++ app/Services/RecentUsersListService.php | 32 ++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 app/Console/Commands/ListRecentlyRegisteredUsers.php create mode 100644 app/Services/RecentUsersListService.php diff --git a/app/Console/Commands/ListRecentlyRegisteredUsers.php b/app/Console/Commands/ListRecentlyRegisteredUsers.php new file mode 100644 index 0000000..66f9f98 --- /dev/null +++ b/app/Console/Commands/ListRecentlyRegisteredUsers.php @@ -0,0 +1,43 @@ +argument('count') ?? self::USER_COUNT; + $users = $recentUserService->execute([ + 'columns' => self::DISPLAY_COLUMNS, + 'count' => $usersCount + ]); + $this->table(array_map('ucfirst', self::DISPLAY_COLUMNS), $users->toArray()); + + return 0; + } +} diff --git a/app/Services/RecentUsersListService.php b/app/Services/RecentUsersListService.php new file mode 100644 index 0000000..5c6ed88 --- /dev/null +++ b/app/Services/RecentUsersListService.php @@ -0,0 +1,32 @@ + + */ +class RecentUsersListService extends AbstractActionService +{ + public function __construct(UserRepository $repository) + { + parent::__construct($repository); + } + + public function execute($listInfo = []) + { + return $this->repository + ->latest() + ->limit($listInfo['count']) + ->get($this->getSelectionColumns($listInfo)); + } + + private function getSelectionColumns(array $listInfo): array + { + return $listInfo['columns'] ?? ['*']; + } +} From 60aefdf05df31c44962536876972dd8d6ef03d68 Mon Sep 17 00:00:00 2001 From: Mohamed Shehata Date: Sat, 12 Feb 2022 01:08:22 +0200 Subject: [PATCH 4/5] update readme --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ca67d92..397ba8a 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Laravel Coding Test +Please follow the following steps to start using the application: -You should have been told which set of tasks to complete. If not please let your contact know. +1. run `composer install` +2. Copy .env.example to .env +3. Setup the database and mail configurations at .env file +4. Run `php artisan migrate` +5. Run `php artisan serve` +6. Access user register page http://localhost:8000/users/create -Feel free to do both sets if you want. - -## Backend +## Backend Tasks 1. Add a page for users to register 2. Use http://postcodes.io/ to ensure that users submit a valid postcode 3. Send a welcome email when a user is registered 4. Add an artisan command to list recently registered users - -## Frontend - -Start the development server using `php artisan serve` and go to http://127.0.0.1:8000/address - -1. Make the address lookup component accessible -2. Style it using bootstrap + - Command `php artisan users:latest` + - You can pass optional argument for command to get the desired number of users `php artisan users:latest 3`. + - If no argument passed to the command it will display the default count which is `10`. \ No newline at end of file From e94b2e5b57dcb902e791ee5d622aec84fe770a96 Mon Sep 17 00:00:00 2001 From: Mohamed Shehata Date: Sat, 12 Feb 2022 11:15:26 +0200 Subject: [PATCH 5/5] update readme --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 397ba8a..4f6f634 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ Please follow the following steps to start using the application: 1. run `composer install` 2. Copy .env.example to .env -3. Setup the database and mail configurations at .env file -4. Run `php artisan migrate` -5. Run `php artisan serve` -6. Access user register page http://localhost:8000/users/create +3. Run `php artisan key:generate` +4. Setup the database and mail configurations at .env file +5. Run `php artisan migrate` +6. Run `php artisan serve` +7. Access user register page http://localhost:8000/users/create ## Backend Tasks