From 4397f91b656ba89d1b3a65b09a4a62cb589d610a Mon Sep 17 00:00:00 2001 From: kusowl Date: Fri, 20 Feb 2026 13:11:25 +0530 Subject: [PATCH] feature: api for user registration --- backend/app/Actions/CreateUserAction.php | 33 ++++++++ .../app/Contracts/InputDataTransferObject.php | 10 +++ .../Contracts/OutputDataTransferObject.php | 8 ++ backend/app/Data/RegisterDTO.php | 28 +++++++ .../Controllers/RegisteredUserController.php | 18 ++++ .../app/Http/Requests/RegisterUserRequest.php | 33 ++++++++ backend/app/Models/User.php | 2 + backend/bootstrap/app.php | 3 +- backend/composer.json | 1 + backend/composer.lock | 65 +++++++++++++- backend/config/cors.php | 34 ++++++++ backend/config/sanctum.php | 84 +++++++++++++++++++ ...25_create_personal_access_tokens_table.php | 33 ++++++++ ...02_20_063750_add_custom_fields_to_user.php | 29 +++++++ backend/routes/api.php | 13 +++ 15 files changed, 392 insertions(+), 2 deletions(-) create mode 100644 backend/app/Actions/CreateUserAction.php create mode 100644 backend/app/Contracts/InputDataTransferObject.php create mode 100644 backend/app/Contracts/OutputDataTransferObject.php create mode 100644 backend/app/Data/RegisterDTO.php create mode 100644 backend/app/Http/Controllers/RegisteredUserController.php create mode 100644 backend/app/Http/Requests/RegisterUserRequest.php create mode 100644 backend/config/cors.php create mode 100644 backend/config/sanctum.php create mode 100644 backend/database/migrations/2026_02_19_133225_create_personal_access_tokens_table.php create mode 100644 backend/database/migrations/2026_02_20_063750_add_custom_fields_to_user.php create mode 100644 backend/routes/api.php diff --git a/backend/app/Actions/CreateUserAction.php b/backend/app/Actions/CreateUserAction.php new file mode 100644 index 0000000..96ef8ff --- /dev/null +++ b/backend/app/Actions/CreateUserAction.php @@ -0,0 +1,33 @@ +user->email = $dto->email; + $this->user->name = $dto->name; + $this->user->city = $dto->city; + $this->user->mobile_number = $dto->mobileNumber; + + // We already have casts to auto hash passwords. + $this->user->password = $dto->password; + + $this->user->save(); + + return $this->user; + } +} diff --git a/backend/app/Contracts/InputDataTransferObject.php b/backend/app/Contracts/InputDataTransferObject.php new file mode 100644 index 0000000..c4919d1 --- /dev/null +++ b/backend/app/Contracts/InputDataTransferObject.php @@ -0,0 +1,10 @@ +validated('name'), + email: $request->validated('email'), + mobileNumber: $request->validated('mobile_number'), + password: $request->validated('password'), + city: $request->validated('city') + ); + } +} diff --git a/backend/app/Http/Controllers/RegisteredUserController.php b/backend/app/Http/Controllers/RegisteredUserController.php new file mode 100644 index 0000000..a66fa73 --- /dev/null +++ b/backend/app/Http/Controllers/RegisteredUserController.php @@ -0,0 +1,18 @@ +execute(RegisterDTO::fromRequest($request)); + + return response()->json(['message' => 'User Registration Successfull']); + } +} diff --git a/backend/app/Http/Requests/RegisterUserRequest.php b/backend/app/Http/Requests/RegisterUserRequest.php new file mode 100644 index 0000000..d5db2cd --- /dev/null +++ b/backend/app/Http/Requests/RegisterUserRequest.php @@ -0,0 +1,33 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'min:3', 'max:50'], + 'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()], + 'email' => 'required|string|email|max:255|unique:users', + 'city' => 'required|string|min:2|max:255', + 'mobile_number' => 'required|min:10|max:25', + ]; + } +} diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index 749c7b7..9fe693c 100644 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -21,6 +21,8 @@ class User extends Authenticatable 'name', 'email', 'password', + 'city', + 'mobile_number', ]; /** diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index c183276..172e8fb 100644 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -7,11 +7,12 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->statefulApi(); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/backend/composer.json b/backend/composer.json index b5c6f92..8cc4e7e 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -11,6 +11,7 @@ "require": { "php": "^8.2", "laravel/framework": "^12.0", + "laravel/sanctum": "^4.0", "laravel/tinker": "^2.10.1" }, "require-dev": { diff --git a/backend/composer.lock b/backend/composer.lock index 58e6223..f8bc90c 100644 --- a/backend/composer.lock +++ b/backend/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": "199095e97ebcedc029e82d15eaea77fa", + "content-hash": "ed4c85d2742e86605a289e878290b8a3", "packages": [ { "name": "brick/math", @@ -1333,6 +1333,69 @@ }, "time": "2026-02-06T12:17:10+00:00" }, + { + "name": "laravel/sanctum", + "version": "v4.3.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "symfony/console": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2026-02-07T17:19:31+00:00" + }, { "name": "laravel/serializable-closure", "version": "v2.0.9", diff --git a/backend/config/cors.php b/backend/config/cors.php new file mode 100644 index 0000000..8a39e6d --- /dev/null +++ b/backend/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/backend/config/sanctum.php b/backend/config/sanctum.php new file mode 100644 index 0000000..44527d6 --- /dev/null +++ b/backend/config/sanctum.php @@ -0,0 +1,84 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort(), + // Sanctum::currentRequestHost(), + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. This will override any values set in the token's + | "expires_at" attribute, but first-party sessions are not affected. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Token Prefix + |-------------------------------------------------------------------------- + | + | Sanctum can prefix new tokens in order to take advantage of numerous + | security scanning initiatives maintained by open source platforms + | that notify developers if they commit tokens into repositories. + | + | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning + | + */ + + 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, + 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + ], + +]; diff --git a/backend/database/migrations/2026_02_19_133225_create_personal_access_tokens_table.php b/backend/database/migrations/2026_02_19_133225_create_personal_access_tokens_table.php new file mode 100644 index 0000000..40ff706 --- /dev/null +++ b/backend/database/migrations/2026_02_19_133225_create_personal_access_tokens_table.php @@ -0,0 +1,33 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/backend/database/migrations/2026_02_20_063750_add_custom_fields_to_user.php b/backend/database/migrations/2026_02_20_063750_add_custom_fields_to_user.php new file mode 100644 index 0000000..0aac976 --- /dev/null +++ b/backend/database/migrations/2026_02_20_063750_add_custom_fields_to_user.php @@ -0,0 +1,29 @@ +string('mobile_number'); + $table->string('city'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('user', function (Blueprint $table) { + $table->dropColumn('mobile_number', 'city'); + }); + } +}; diff --git a/backend/routes/api.php b/backend/routes/api.php new file mode 100644 index 0000000..4f4b61c --- /dev/null +++ b/backend/routes/api.php @@ -0,0 +1,13 @@ +user(); +})->middleware('auth:sanctum'); + +Route::middleware('guest')->group(function () { + Route::post('/register', RegisteredUserController::class); +});