diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..b218b40 --- /dev/null +++ b/.env.testing @@ -0,0 +1,59 @@ +APP_NAME="POST API TESTING" +APP_ENV=local +APP_KEY=base64:x1Pl1NOaAyvLZVjkQa7vfi3rM6O32QH49XAy66Lsy8U= +APP_DEBUG=true +APP_URL=http://localhost:8000 + +AUTH_API=http://localhost:8081 + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=pgsql +DB_HOST=127.0.0.1 +DB_PORT=5432 +DB_DATABASE=test +DB_USERNAME=postgres +DB_PASSWORD=postgres + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DRIVER=local +QUEUE_CONNECTION=database +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mailhog +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS=null +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +JWT_SECRET=idJBIg1cm0cEzoRtTuxQiNJISMkzNwR9POcO0zOwQdRwQiLKSxENcM8YzSFI7dZi + +XENDIT_PRIVATE_KEY="xnd_development_lWG7Z91cZzrxSG76nDzWAHgxSqv5NdYzwRNqVAZZll5MXMe5iJJq38uxPfCwD5i" +XENDIT_WEBHOOK_TOKEN="8ECHQlXMK6xbKqIJpWHJiz3hrXN06gkHboVSiHM1W1uphx4y" diff --git a/app/Http/Controllers/Auth/CurrentController.php b/app/Http/Controllers/Auth/CurrentController.php new file mode 100644 index 0000000..9cd30be --- /dev/null +++ b/app/Http/Controllers/Auth/CurrentController.php @@ -0,0 +1,19 @@ +user(); + return new CurrentResource($user); + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php new file mode 100644 index 0000000..ad0c0dd --- /dev/null +++ b/app/Http/Controllers/Auth/LoginController.php @@ -0,0 +1,24 @@ +validated(); + $data = $authRepository->login($params); + return [ + "success" => true, + "data" => $data + ]; + } +} diff --git a/app/Http/Controllers/Auth/LogoutController.php b/app/Http/Controllers/Auth/LogoutController.php new file mode 100644 index 0000000..96c90d6 --- /dev/null +++ b/app/Http/Controllers/Auth/LogoutController.php @@ -0,0 +1,18 @@ +logout(); + } +} diff --git a/app/Http/Controllers/Auth/User/ListController.php b/app/Http/Controllers/Auth/User/ListController.php new file mode 100644 index 0000000..3deb69f --- /dev/null +++ b/app/Http/Controllers/Auth/User/ListController.php @@ -0,0 +1,17 @@ + [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', - \Illuminate\Routing\Middleware\SubstituteBindings::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class ], ]; diff --git a/app/Http/Middleware/JsonResponseMiddleware.php b/app/Http/Middleware/JsonResponseMiddleware.php new file mode 100644 index 0000000..b9714b7 --- /dev/null +++ b/app/Http/Middleware/JsonResponseMiddleware.php @@ -0,0 +1,52 @@ +responseFactory = $responseFactory; + } + + /** + * Handle an incoming request. + * + * @param Request $request + * @param Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + // First, set the header so any other middleware knows we're + // dealing with a should-be JSON response. + $request->headers->set('Accept', 'application/json'); + + // Get the response + $response = $next($request); + + // If the response is not strictly a JsonResponse, we make it + if (!$response instanceof JsonResponse) { + $response = $this->responseFactory->json( + $response->content(), + $response->status(), + $response->headers->all() + ); + } + + return $response; + } +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php new file mode 100644 index 0000000..68a6a77 --- /dev/null +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -0,0 +1,29 @@ +|string> + */ + public function rules(): array + { + return [ + "email" => "required", + "password" => "required" + ]; + } +} diff --git a/app/Http/Requests/Auth/User/ListRequest.php b/app/Http/Requests/Auth/User/ListRequest.php new file mode 100644 index 0000000..01603d8 --- /dev/null +++ b/app/Http/Requests/Auth/User/ListRequest.php @@ -0,0 +1,31 @@ +user(); + $user->load("roles"); + + return false; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Resources/Auth/CurrentResource.php b/app/Http/Resources/Auth/CurrentResource.php new file mode 100644 index 0000000..5104650 --- /dev/null +++ b/app/Http/Resources/Auth/CurrentResource.php @@ -0,0 +1,23 @@ + + */ + public function toArray(Request $request): array + { + return [ + "id" => $this->id, + "name" => $this->name, + "email" => $this->email + ]; + } +} diff --git a/app/Models/Permission.php b/app/Models/Permission.php new file mode 100644 index 0000000..40d4034 --- /dev/null +++ b/app/Models/Permission.php @@ -0,0 +1,11 @@ +hasMany(Permission::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 4d7f70f..f61f74b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -42,4 +42,8 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; + + public function roles(){ + return $this->hasMany(Role::class)->with("permissions"); + } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 1cf5f15..55b12e0 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -19,6 +19,8 @@ class RouteServiceProvider extends ServiceProvider */ public const HOME = '/home'; + protected $apiNamespace = 'App\Http\Controllers'; + /** * Define your route model bindings, pattern filters, and other route configuration. */ @@ -29,6 +31,11 @@ class RouteServiceProvider extends ServiceProvider }); $this->routes(function () { + Route::middleware('api') + ->prefix('auth') + ->namespace("{$this->apiNamespace}\Auth") + ->group(base_path('routes/auth.php')); + Route::middleware('api') ->prefix('api') ->group(base_path('routes/api.php')); diff --git a/app/Repositories/Auth/AuthRepository.php b/app/Repositories/Auth/AuthRepository.php new file mode 100644 index 0000000..45a7ce7 --- /dev/null +++ b/app/Repositories/Auth/AuthRepository.php @@ -0,0 +1,34 @@ +first(); + if (!$user){ + throw ValidationException::withMessages([ + "email" => "Email is not exists!" + ]); + } + + if (!Hash::check($params["password"],$user->password)){ + throw ValidationException::withMessages([ + "password" => "Password is wrong!" + ]); + } + + $token = $user->createToken($device); + return [$user, $token]; + } + + public function logout(){ + auth()->user()->currentAccessToken()->delete(); + } +} diff --git a/config/database.php b/config/database.php index 137ad18..c35c382 100644 --- a/config/database.php +++ b/config/database.php @@ -35,6 +35,21 @@ return [ 'connections' => [ + 'test' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST_TEST', '127.0.0.1'), + 'port' => env('DB_PORT_TEST', '5432'), + 'database' => env('DB_DATABASE_TEST', 'test'), + 'username' => env('DB_USERNAME_TEST', 'postgres'), + 'password' => env('DB_PASSWORD_TEST', 'postgres'), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'schema' => 'public', + 'sslmode' => 'prefer', + ], + 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 444fafb..621a24e 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -4,12 +4,14 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration +class CreateUsersTable extends Migration { /** * Run the migrations. + * + * @return void */ - public function up(): void + public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); @@ -24,9 +26,11 @@ return new class extends Migration /** * Reverse the migrations. + * + * @return void */ - public function down(): void + public function down() { Schema::dropIfExists('users'); } -}; +} diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2014_10_12_100000_create_password_resets_table.php new file mode 100644 index 0000000..0ee0a36 --- /dev/null +++ b/database/migrations/2014_10_12_100000_create_password_resets_table.php @@ -0,0 +1,32 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_resets'); + } +} diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php index 249da81..6aa6d74 100644 --- a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php +++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php @@ -4,12 +4,14 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration +class CreateFailedJobsTable extends Migration { /** * Run the migrations. + * + * @return void */ - public function up(): void + public function up() { Schema::create('failed_jobs', function (Blueprint $table) { $table->id(); @@ -24,9 +26,11 @@ return new class extends Migration /** * Reverse the migrations. + * + * @return void */ - public function down(): void + public function down() { Schema::dropIfExists('failed_jobs'); } -}; +} diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php index e828ad8..7af38c5 100644 --- a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -4,12 +4,14 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration +class CreatePersonalAccessTokensTable extends Migration { /** * Run the migrations. + * + * @return void */ - public function up(): void + public function up() { Schema::create('personal_access_tokens', function (Blueprint $table) { $table->id(); @@ -25,9 +27,11 @@ return new class extends Migration /** * Reverse the migrations. + * + * @return void */ - public function down(): void + public function down() { Schema::dropIfExists('personal_access_tokens'); } -}; +} diff --git a/database/migrations/2023_04_30_013254_create_roles_table.php b/database/migrations/2023_04_30_013254_create_roles_table.php new file mode 100644 index 0000000..d85b7cf --- /dev/null +++ b/database/migrations/2023_04_30_013254_create_roles_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('roles'); + } +} diff --git a/database/migrations/2023_04_30_013325_create_permissions_table.php b/database/migrations/2023_04_30_013325_create_permissions_table.php new file mode 100644 index 0000000..3369b37 --- /dev/null +++ b/database/migrations/2023_04_30_013325_create_permissions_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('module')->nullable(); + $table->string('feature')->nullable(); + $table->string('action')->nullable(); + $table->string('code')->unique(); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('permissions'); + } +} diff --git a/database/migrations/2023_04_30_013512_create_role_permission_table.php b/database/migrations/2023_04_30_013512_create_role_permission_table.php new file mode 100644 index 0000000..3edb864 --- /dev/null +++ b/database/migrations/2023_04_30_013512_create_role_permission_table.php @@ -0,0 +1,31 @@ +bigInteger("role_id"); + $table->bigInteger("permission_id"); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('role_permission'); + } +} diff --git a/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php b/database/migrations/2025_01_25_090322_create_user_role_table.php similarity index 57% rename from database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php rename to database/migrations/2025_01_25_090322_create_user_role_table.php index 81a7229..b9bab77 100644 --- a/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php +++ b/database/migrations/2025_01_25_090322_create_user_role_table.php @@ -11,10 +11,9 @@ return new class extends Migration */ public function up(): void { - Schema::create('password_reset_tokens', function (Blueprint $table) { - $table->string('email')->primary(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); + Schema::create('user_role', function (Blueprint $table) { + $table->bigInteger('user_id'); + $table->bigInteger('role_id'); }); } @@ -23,6 +22,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('user_role'); } }; diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php new file mode 100644 index 0000000..3194903 --- /dev/null +++ b/database/seeders/PermissionSeeder.php @@ -0,0 +1,18 @@ +middleware("auth:sanctum"); +Route::get('current', 'CurrentController')->middleware("auth:sanctum"); + +Route::group(["namespace" => "User", "prefix" => "user", "middleware" => "auth:sanctum"], function () { + Route::get('/', 'ListController')->middleware("auth:sanctum"); +}); diff --git a/routes/web.php b/routes/web.php index d259f33..a248b17 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,5 +14,5 @@ use Illuminate\Support\Facades\Route; */ Route::get('/', function () { - return view('welcome'); + return "HELLO WORLD"; }); diff --git a/tests/Feature/Auth/CurrentTest.php b/tests/Feature/Auth/CurrentTest.php new file mode 100644 index 0000000..11d4a48 --- /dev/null +++ b/tests/Feature/Auth/CurrentTest.php @@ -0,0 +1,28 @@ +create(); + Sanctum::actingAs($user); + + $response = $this->get('/auth/current'); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/Auth/LoginTest.php b/tests/Feature/Auth/LoginTest.php new file mode 100644 index 0000000..4d9b157 --- /dev/null +++ b/tests/Feature/Auth/LoginTest.php @@ -0,0 +1,29 @@ +create(); + + $response = $this->post('/auth/login',[ + "email" => $user->email, + "password" => "password", + ]); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/Auth/User/ListTest.php b/tests/Feature/Auth/User/ListTest.php new file mode 100644 index 0000000..4c5b5fd --- /dev/null +++ b/tests/Feature/Auth/User/ListTest.php @@ -0,0 +1,29 @@ +create(); + Sanctum::actingAs($user); + + $response = $this->get('/auth/user/'); + + $response->assertStatus(200); + } +}