diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php old mode 100644 new mode 100755 index f62f69bb..9f73d3cc --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -5,29 +5,18 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; +use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Passport\HasApiTokens; -#[Fillable(['name', 'email', 'password'])] -#[Hidden(['password', 'remember_token'])] +#[Guarded(['id'])] +#[Hidden(['password'])] class User extends Authenticatable { /** @use HasFactory */ use HasFactory, Notifiable, HasApiTokens; - - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - ]; - } + protected $guarded = ['id']; } diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php old mode 100644 new mode 100755 index bbedac67..1039668a --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; use Laravel\Passport\Passport; +use Carbon\CarbonInterval; class AppServiceProvider extends ServiceProvider { @@ -21,5 +22,6 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Passport::enablePasswordGrant(); + Passport::personalAccessTokensExpireIn(CarbonInterval::minutes(1)); } } diff --git a/backend/app/Traits/ApiResponse.php b/backend/app/Traits/ApiResponse.php new file mode 100755 index 00000000..5c9997d8 --- /dev/null +++ b/backend/app/Traits/ApiResponse.php @@ -0,0 +1,31 @@ +json([ + 'data' => $data + ], $statusCode); + } + + $message = $message ?? __('messages.successful'); + + return response()->json([ + 'message' => $message + ], $statusCode); + } + + public function errorResponse(string $message, string $type = 'logical_exception', int $statusCode = 422): JsonResponse + { + return response()->json([ + 'type' => $type, + 'message' => $message + ], $statusCode); + } +} diff --git a/backend/app/User/Controller/Auth/AuthController.php b/backend/app/User/Controller/Auth/AuthController.php new file mode 100755 index 00000000..cbd4f33d --- /dev/null +++ b/backend/app/User/Controller/Auth/AuthController.php @@ -0,0 +1,65 @@ +create([ + 'username' => $request->username, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + $request->session()->regenerate(); + + return $this->successResponse(); + } + + public function login(LoginRequest $request): JsonResponse + { + $credentials = ['password' => $request->password,]; + + if (filter_var($request->login, FILTER_VALIDATE_EMAIL)) { + $credentials['email'] = $request->login; + } else { + $credentials['username'] = $request->login; + } + + if (Auth::attempt($credentials)) + { + $request->session()->regenerate(); + + $user = Auth::user(); + + return $this->successResponse(new UserResource($user)); + } + + return $this->errorResponse(__('messages.incorrect_or_username_password')); + } + + public function logout(Request $request): JsonResponse + { + Auth::logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return $this->successResponse(); + } +} diff --git a/backend/app/User/Controller/ProfileController.php b/backend/app/User/Controller/ProfileController.php new file mode 100644 index 00000000..c3c07706 --- /dev/null +++ b/backend/app/User/Controller/ProfileController.php @@ -0,0 +1,56 @@ +successResponse(new UserResource(Auth::user())); + } + + public function edit(EditRequest $request): JsonResponse + { + $user = Auth::user(); + + $user->update([ + 'first_name' => $request->first_name, + 'last_name' => $request->last_name, + 'national_id' => $request->national_id, + 'address' => $request->address, + 'postal_code' => $request->postal_code, + 'account_number' => $request->account_number, + 'phone_number' => $request->phone_number, + 'province_id' => $request->province_id, + 'city_id' => $request->city_id, + ]); + + return $this->successResponse(); + } + + public function changePassword(ChangePasswordRequest $request): JsonResponse + { + $user = Auth::user(); + + if (! Hash::check($request->current_password, $user->password)) { + return $this->errorResponse(__('messages.incorrect_current_password')); + } + + $user->update([ + 'password' => Hash::make($request->new_password), + ]); + + return $this->successResponse(); + } +} diff --git a/backend/app/User/Requests/Auth/LoginRequest.php b/backend/app/User/Requests/Auth/LoginRequest.php new file mode 100755 index 00000000..d9b9c881 --- /dev/null +++ b/backend/app/User/Requests/Auth/LoginRequest.php @@ -0,0 +1,29 @@ + + */ + public function rules(): array + { + return [ + 'login' => 'required|string', + 'password' => 'required|string', + ]; + } +} diff --git a/backend/app/User/Requests/Auth/RegisterRequest.php b/backend/app/User/Requests/Auth/RegisterRequest.php new file mode 100755 index 00000000..1c8055fe --- /dev/null +++ b/backend/app/User/Requests/Auth/RegisterRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'username' => 'required|unique:users,username', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6|confirmed:', + ]; + } +} diff --git a/backend/app/User/Requests/Profile/ChangePasswordRequest.php b/backend/app/User/Requests/Profile/ChangePasswordRequest.php new file mode 100644 index 00000000..60e53c56 --- /dev/null +++ b/backend/app/User/Requests/Profile/ChangePasswordRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'current_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6', + 'new_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:6', + ]; + } +} diff --git a/backend/app/User/Requests/Profile/EditRequest.php b/backend/app/User/Requests/Profile/EditRequest.php new file mode 100644 index 00000000..ddcfcba8 --- /dev/null +++ b/backend/app/User/Requests/Profile/EditRequest.php @@ -0,0 +1,37 @@ + + */ + public function rules(): array + { + return [ + 'first_name' => 'required|string', + 'last_name' => 'required|string', + 'national_id' => 'required', + 'address' => 'required', + 'postal_code' => 'required', + 'account_number' => 'required', + 'phone_number' => 'required|regex:/^964\d{9}$/|numeric|digits:11', + 'province_id' => 'required', + 'city_id' => 'required', + ]; + } +} diff --git a/backend/app/User/Resources/UserResource.php b/backend/app/User/Resources/UserResource.php new file mode 100755 index 00000000..55f98563 --- /dev/null +++ b/backend/app/User/Resources/UserResource.php @@ -0,0 +1,23 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'username' => $this->username, + 'email' => $this->email, + ]; + } +} diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php old mode 100644 new mode 100755 index c3928c57..6de85c67 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -12,7 +12,7 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->web()->validateCsrfTokens(['*']); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/backend/config/auth.php b/backend/config/auth.php old mode 100644 new mode 100755 diff --git a/backend/config/cors.php b/backend/config/cors.php new file mode 100755 index 00000000..a76728a2 --- /dev/null +++ b/backend/config/cors.php @@ -0,0 +1,34 @@ + ['*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => true, + +]; diff --git a/backend/config/session.php b/backend/config/session.php old mode 100644 new mode 100755 diff --git a/backend/database/factories/UserFactory.php b/backend/database/factories/UserFactory.php old mode 100644 new mode 100755 index c4ceb074..b3301b8b --- a/backend/database/factories/UserFactory.php +++ b/backend/database/factories/UserFactory.php @@ -25,7 +25,7 @@ class UserFactory extends Factory public function definition(): array { return [ - 'name' => fake()->name(), + 'username' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php old mode 100644 new mode 100755 index 05fb5d9e..87b84e12 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -13,18 +13,23 @@ return new class extends Migration { Schema::create('users', function (Blueprint $table) { $table->id(); - $table->string('name'); + $table->string('username'); + $table->string('first_name')->nullable(); + $table->string('last_name')->nullable(); $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); + $table->tinyInteger('status')->default(0); + $table->string('national_id')->nullable(); + $table->string('address')->nullable(); + $table->string('postal_code')->nullable(); + $table->string('account_number')->nullable(); + $table->string('phone_number')->nullable(); + $table->string('province_id')->nullable(); + $table->string('province_name')->nullable(); + $table->string('city_id')->nullable(); + $table->string('city_name')->nullable(); - Schema::create('password_reset_tokens', function (Blueprint $table) { - $table->string('email')->primary(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); + $table->timestamps(); }); Schema::create('sessions', function (Blueprint $table) { @@ -43,7 +48,6 @@ return new class extends Migration public function down(): void { Schema::dropIfExists('users'); - Schema::dropIfExists('password_reset_tokens'); Schema::dropIfExists('sessions'); } }; diff --git a/backend/database/seeders/DatabaseSeeder.php b/backend/database/seeders/DatabaseSeeder.php old mode 100644 new mode 100755 index 6b901f8b..55af5ff8 --- a/backend/database/seeders/DatabaseSeeder.php +++ b/backend/database/seeders/DatabaseSeeder.php @@ -18,8 +18,9 @@ class DatabaseSeeder extends Seeder // User::factory(10)->create(); User::factory()->create([ - 'name' => 'Test User', + 'username' => 'Test User', 'email' => 'test@example.com', + 'password' => 'password123@', ]); } } diff --git a/backend/routes/api.php b/backend/routes/api.php old mode 100644 new mode 100755 index f35f6f84..56897756 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,8 +1,10 @@ user(); })->middleware('auth:api'); + diff --git a/backend/routes/web.php b/backend/routes/web.php old mode 100644 new mode 100755 index 86a06c53..8dc5599e --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -1,7 +1,24 @@ name('Auth.') + ->controller(AuthController::class) + ->group(function () { + Route::post('register', 'register')->name('register'); + Route::post('login', 'login')->name('login'); + Route::post('logout', 'logout')->name('logout'); + }); + +Route::prefix('profile') + ->name('profile.') + ->middleware('auth') + ->controller(ProfileController::class) + ->group(function () { + Route::get('info', 'info')->name('info'); + Route::post('edit', 'edit')->name('edit'); + Route::post('change_password', 'changePassword')->name('changePassword'); + });