This commit is contained in:
Husnu Setiawan 2025-01-26 09:19:44 +07:00
parent 52d77b452a
commit 2254c67eff
32 changed files with 658 additions and 19 deletions

59
.env.testing Normal file
View File

@ -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"

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Resources\Auth\CurrentResource;
class CurrentController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request)
{
$user = auth()->user();
return new CurrentResource($user);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Auth\LoginRequest;
use App\Repositories\Auth\AuthRepository;
class LoginController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(LoginRequest $request, AuthRepository $authRepository)
{
$params = $request->validated();
$data = $authRepository->login($params);
return [
"success" => true,
"data" => $data
];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Repositories\Auth\AuthRepository;
class LogoutController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request, AuthRepository $authRepository)
{
$authRepository->logout();
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Controllers\Auth\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ListController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request)
{
//
}
}

View File

@ -21,6 +21,7 @@ class Kernel extends HttpKernel
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\JsonResponseMiddleware::class
]; ];
/** /**
@ -41,7 +42,7 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api', \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class
], ],
]; ];

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
class JsonResponseMiddleware
{
/**
* @var ResponseFactory
*/
protected $responseFactory;
/**
* JsonResponseMiddleware constructor.
*/
public function __construct(ResponseFactory $responseFactory)
{
$this->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;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
"email" => "required",
"password" => "required"
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Auth\User;
use Illuminate\Foundation\Http\FormRequest;
class ListRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
$user = auth()->user();
$user->load("roles");
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Resources\Auth;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CurrentResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
"id" => $this->id,
"name" => $this->name,
"email" => $this->email
];
}
}

11
app/Models/Permission.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Permission extends Model
{
use HasFactory;
}

15
app/Models/Role.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
use HasFactory;
public function permissions(){
return $this->hasMany(Permission::class);
}
}

View File

@ -42,4 +42,8 @@ class User extends Authenticatable
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'password' => 'hashed', 'password' => 'hashed',
]; ];
public function roles(){
return $this->hasMany(Role::class)->with("permissions");
}
} }

View File

@ -19,6 +19,8 @@ class RouteServiceProvider extends ServiceProvider
*/ */
public const HOME = '/home'; public const HOME = '/home';
protected $apiNamespace = 'App\Http\Controllers';
/** /**
* Define your route model bindings, pattern filters, and other route configuration. * Define your route model bindings, pattern filters, and other route configuration.
*/ */
@ -29,6 +31,11 @@ class RouteServiceProvider extends ServiceProvider
}); });
$this->routes(function () { $this->routes(function () {
Route::middleware('api')
->prefix('auth')
->namespace("{$this->apiNamespace}\Auth")
->group(base_path('routes/auth.php'));
Route::middleware('api') Route::middleware('api')
->prefix('api') ->prefix('api')
->group(base_path('routes/api.php')); ->group(base_path('routes/api.php'));

View File

@ -0,0 +1,34 @@
<?php
namespace App\Repositories\Auth;
use App\Models\User;
use Hash;
class AuthRepository
{
public function login($params){
$device = @$params["device"] ?? "default" ;
$user = User::where("email",$params["email"])->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();
}
}

View File

@ -35,6 +35,21 @@ return [
'connections' => [ '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' => [ 'sqlite' => [
'driver' => 'sqlite', 'driver' => 'sqlite',
'url' => env('DATABASE_URL'), 'url' => env('DATABASE_URL'),

View File

@ -4,12 +4,14 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration class CreateUsersTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*
* @return void
*/ */
public function up(): void public function up()
{ {
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id(); $table->id();
@ -24,9 +26,11 @@ return new class extends Migration
/** /**
* Reverse the migrations. * Reverse the migrations.
*
* @return void
*/ */
public function down(): void public function down()
{ {
Schema::dropIfExists('users'); Schema::dropIfExists('users');
} }
}; }

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('password_resets');
}
}

View File

@ -4,12 +4,14 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration class CreateFailedJobsTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*
* @return void
*/ */
public function up(): void public function up()
{ {
Schema::create('failed_jobs', function (Blueprint $table) { Schema::create('failed_jobs', function (Blueprint $table) {
$table->id(); $table->id();
@ -24,9 +26,11 @@ return new class extends Migration
/** /**
* Reverse the migrations. * Reverse the migrations.
*
* @return void
*/ */
public function down(): void public function down()
{ {
Schema::dropIfExists('failed_jobs'); Schema::dropIfExists('failed_jobs');
} }
}; }

View File

@ -4,12 +4,14 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration class CreatePersonalAccessTokensTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
*
* @return void
*/ */
public function up(): void public function up()
{ {
Schema::create('personal_access_tokens', function (Blueprint $table) { Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id(); $table->id();
@ -25,9 +27,11 @@ return new class extends Migration
/** /**
* Reverse the migrations. * Reverse the migrations.
*
* @return void
*/ */
public function down(): void public function down()
{ {
Schema::dropIfExists('personal_access_tokens'); Schema::dropIfExists('personal_access_tokens');
} }
}; }

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRolesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('roles');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePermissionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->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');
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRolePermissionTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('role_permission', function (Blueprint $table) {
$table->bigInteger("role_id");
$table->bigInteger("permission_id");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('role_permission');
}
}

View File

@ -11,10 +11,9 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('password_reset_tokens', function (Blueprint $table) { Schema::create('user_role', function (Blueprint $table) {
$table->string('email')->primary(); $table->bigInteger('user_id');
$table->string('token'); $table->bigInteger('role_id');
$table->timestamp('created_at')->nullable();
}); });
} }
@ -23,6 +22,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('password_reset_tokens'); Schema::dropIfExists('user_role');
} }
}; };

View File

@ -0,0 +1,18 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class PermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$permissions = file_get_contents(__DIR__ . "/permissions.csv");
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -0,0 +1,5 @@
Auth,User,*,auth.user.*,All operation data user
Auth,User,*,auth.user.read,Show list data user
Auth,User,*,auth.user.create,Create data user
Auth,User,*,auth.user.update,Update data user
Auth,User,*,auth.user.delete,Delete data user
1 Auth User * auth.user.* All operation data user
2 Auth User * auth.user.read Show list data user
3 Auth User * auth.user.create Create data user
4 Auth User * auth.user.update Update data user
5 Auth User * auth.user.delete Delete data user

12
routes/auth.php Normal file
View File

@ -0,0 +1,12 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('login', 'LoginController');
Route::post('logout', 'LogoutController')->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");
});

View File

@ -14,5 +14,5 @@ use Illuminate\Support\Facades\Route;
*/ */
Route::get('/', function () { Route::get('/', function () {
return view('welcome'); return "HELLO WORLD";
}); });

View File

@ -0,0 +1,28 @@
<?php
namespace Tests\Feature\Auth;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\User;
use Hash;
use Laravel\Sanctum\Sanctum;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CurrentTest extends TestCase
{
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function test_success(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user);
$response = $this->get('/auth/current');
$response->assertStatus(200);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Tests\Feature\Auth;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\User;
use Hash;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class LoginTest extends TestCase
{
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function test_success(): void
{
$user = User::factory()->create();
$response = $this->post('/auth/login',[
"email" => $user->email,
"password" => "password",
]);
$response->assertStatus(200);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Tests\Feature\Auth\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Role;
use App\Models\Permission;
use App\Models\User;
use Laravel\Sanctum\Sanctum;
class ListTest extends TestCase
{
/**
* A basic feature test example.
*/
public function test_success(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user);
$response = $this->get('/auth/user/');
$response->assertStatus(200);
}
}