diff --git a/app/Concerns/HasUuid.php b/app/Concerns/HasUuid.php new file mode 100644 index 0000000..c334f71 --- /dev/null +++ b/app/Concerns/HasUuid.php @@ -0,0 +1,14 @@ +id = $model->id ?? Str::orderedUuid()->toString(); + }); + } +} diff --git a/app/Constants/Permissions.php b/app/Constants/Permissions.php new file mode 100644 index 0000000..2c7d924 --- /dev/null +++ b/app/Constants/Permissions.php @@ -0,0 +1,20 @@ + $this->username, + 'password' => $this->password, + 'role' => $this->role, + ]; + } +} diff --git a/app/Dto/UpdateUserDto.php b/app/Dto/UpdateUserDto.php new file mode 100644 index 0000000..3c7ae27 --- /dev/null +++ b/app/Dto/UpdateUserDto.php @@ -0,0 +1,22 @@ + $this->username, + 'password' => $this->password, + 'role' => $this->role + ]; + } +} diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php new file mode 100644 index 0000000..e73aa0c --- /dev/null +++ b/app/Http/Controllers/Auth/AuthController.php @@ -0,0 +1,37 @@ +execute( + new CreateUserDto( + username: $request->input('username'), + password: $request->input('password'), + role: $request->input('role') + ) + ); + + return AuthUserResource::make($user); + } + + public function store(LoginRequest $request) + { + $user = User::where('username', $request->username)->first(); + + abort_if(!$user || !Hash::check($request->password, $user->password), 404, 'User not found!'); + + return AuthUserResource::make($user); + } +} diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php new file mode 100644 index 0000000..dd9bd8b --- /dev/null +++ b/app/Http/Controllers/ProjectController.php @@ -0,0 +1,65 @@ +authorize('view', Project::class); + + return ProjectResource::collection(Project::all()); + } + + public function store(CreateProjectRequest $request) + { + $this->authorize('create', Project::class); + + return ProjectResource::make(Project::create($request->validated())); + } + + public function show(Project $project) + { + $this->authorize('view', Project::class); + + return ProjectResource::make($project); + } + + public function updateWithPut(CreateProjectRequest $createProjectRequest, Project $project) + { + $this->authorize('update', Project::class); + + $project->name = $createProjectRequest->input('name', $project->name); + $project->timestamps = false; + $project->save(); + + return ProjectResource::make($project); + } + + public function updateWithPatch(CreateProjectRequest $createProjectRequest, Project $project) + { + $this->authorize('update', Project::class); + + $project->update($createProjectRequest->validated()); + + return ProjectResource::make($project); + } + + public function destroy(Project $project) + { + $this->authorize('delete', Project::class); + + $project->delete(); + + return response()->json([ + 'message' => $project->name . ' has been deleted!', + 'success' => true + ]); + } +} diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php new file mode 100644 index 0000000..36befd9 --- /dev/null +++ b/app/Http/Controllers/RoleController.php @@ -0,0 +1,10 @@ +options(); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..6b756c2 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,78 @@ +execute( + new CreateUserDto( + username: $request->input('username'), + password: $request->input('password'), + role: $request->input('role') + )); + + return UserResource::make($user); + } + + public function show(User $user) + { + return UserResource::make($user); + } + + // definitely personal choice here to use CreateUserRequest instead of creating other form request + // as i think it would be quite similar + public function updateWithPut(CreateUserRequest $request, User $user, UpdateUserWithPut $updateUserWithPut) + { + $user = $updateUserWithPut->execute( + $user, + new CreateUserDto( + username: $request->input('username'), + password: $request->input('password'), + role: $request->input('role'), + ) + ); + + return UserResource::make($user); + } + + public function updateWithPatch(PartiallyUpdateUserRequest $request, User $user, UpdateUserWithPatch $updateUserWithPatch) + { + $user = $updateUserWithPatch->execute( + $user, + new UpdateUserDto( + username: $request->input('username'), + password: $request->input('password'), + role: $request->input('role'), + ) + ); + + return UserResource::make($user); + } + + public function destroy(User $user) + { + $user->delete(); + + return response()->json([ + 'message' => $user->username . ' has been deleted!', + 'success' => true + ]); + } +} diff --git a/app/Http/Requests/Projects/CreateProjectRequest.php b/app/Http/Requests/Projects/CreateProjectRequest.php new file mode 100644 index 0000000..75f5a20 --- /dev/null +++ b/app/Http/Requests/Projects/CreateProjectRequest.php @@ -0,0 +1,23 @@ +project) { + $nameAdditionalRule = $nameAdditionalRule->ignore($this->project->id); + } + return [ + 'name' => [ + 'required', + $nameAdditionalRule, + ] + ]; + } +} diff --git a/app/Http/Requests/Users/CreateUserRequest.php b/app/Http/Requests/Users/CreateUserRequest.php new file mode 100644 index 0000000..8ac87ab --- /dev/null +++ b/app/Http/Requests/Users/CreateUserRequest.php @@ -0,0 +1,46 @@ +user) { + $usernameAdditionalRule = $usernameAdditionalRule->ignore($this->user->id); + } + + return [ + 'username' => [ + 'required', + $usernameAdditionalRule, + ], + 'password' => 'required|confirmed', + 'role' => [ + 'required', + Rule::in(collect(app('roleService')->roles)->map->value()->toArray()) + ] + ]; + } + + protected function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'errors' => $validator->errors(), + ], 422)); + } + + public function messages() + { + return [ + 'role.in' => 'Role should be either admin, product_owner, or dev', + ]; + } +} diff --git a/app/Http/Requests/Users/LoginRequest.php b/app/Http/Requests/Users/LoginRequest.php new file mode 100644 index 0000000..9a59dab --- /dev/null +++ b/app/Http/Requests/Users/LoginRequest.php @@ -0,0 +1,24 @@ + 'required', + 'password' => 'required' + ]; + } + + protected function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'errors' => $validator->errors(), + ], 422)); + } +} diff --git a/app/Http/Requests/Users/PartiallyUpdateUserRequest.php b/app/Http/Requests/Users/PartiallyUpdateUserRequest.php new file mode 100644 index 0000000..f409e49 --- /dev/null +++ b/app/Http/Requests/Users/PartiallyUpdateUserRequest.php @@ -0,0 +1,39 @@ + [ + 'sometimes', + Rule::unique('users', 'username')->ignore($this->user->id) + ], + 'password' => 'sometimes|confirmed', + 'role' => [ + 'sometimes', + Rule::in(collect(app('roleService')->roles)->map->value()->toArray()) + ] + ]; + } + + protected function failedValidation(Validator $validator) + { + throw new HttpResponseException(response()->json([ + 'errors' => $validator->errors(), + ], 422)); + } + + public function messages() + { + return [ + 'role.in' => 'Role should be either admin, product_owner, or dev', + ]; + } +} diff --git a/app/Http/Resources/AuthUserResource.php b/app/Http/Resources/AuthUserResource.php new file mode 100644 index 0000000..a693040 --- /dev/null +++ b/app/Http/Resources/AuthUserResource.php @@ -0,0 +1,15 @@ + $this->username, + 'token' => $this->createToken('auth')->plainTextToken, + ]; + } +} diff --git a/app/Http/Resources/ProjectResource.php b/app/Http/Resources/ProjectResource.php new file mode 100644 index 0000000..e483c0a --- /dev/null +++ b/app/Http/Resources/ProjectResource.php @@ -0,0 +1,12 @@ + 'string' + ]; + + public $incrementing = false; +} diff --git a/app/Models/ProjectUser.php b/app/Models/ProjectUser.php new file mode 100644 index 0000000..2baea65 --- /dev/null +++ b/app/Models/ProjectUser.php @@ -0,0 +1,16 @@ + 'string' + ]; + + public $incrementing = false; +} diff --git a/app/Models/User.php b/app/Models/User.php index 8996368..eb1020a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,15 +2,19 @@ namespace App\Models; -use Illuminate\Contracts\Auth\MustVerifyEmail; +use App\Concerns\HasUuid; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; +use Illuminate\Support\Str; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; + use HasUuid; + use SoftDeletes; /** * The attributes that are mass assignable. @@ -18,9 +22,9 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'name', - 'email', + 'username', 'password', + 'role' ]; /** @@ -33,12 +37,14 @@ class User extends Authenticatable 'remember_token', ]; - /** - * The attributes that should be cast. - * - * @var array - */ protected $casts = [ - 'email_verified_at' => 'datetime', + 'id' => 'string' ]; + + public $incrementing = false; + + public function resolveRouteBinding($value, $field = null) + { + return $this->where('id', $value)->firstOrFail(); + } } diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php new file mode 100644 index 0000000..a8670d2 --- /dev/null +++ b/app/Policies/ProjectPolicy.php @@ -0,0 +1,32 @@ +can($user->role, Permissions::CREATE_PROJECT); + } + + public function view(User $user) + { + return app('roleService')->can($user->role, Permissions::VIEW_PROJECT); + } + + public function update(User $user) + { + return app('roleService')->can($user->role, Permissions::UPDATE_PROJECT); + } + + public function delete(User $user) + { + return app('roleService')->can($user->role, Permissions::DELETE_PROJECT); + } +} diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 0000000..a4368a4 --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,32 @@ +can($user->role, Permissions::CREATE_USER); + } + + public function update(User $user) + { + return app('roleService')->can($user->role, Permissions::UPDATE_USER); + } + + public function delete(User $user) + { + return app('roleService')->can($user->role, Permissions::DELETE_USER); + } + + public function view(User $user) + { + return app('roleService')->can($user->role, Permissions::VIEW_USER); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ee8ca5b..d4f90e1 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,10 @@ namespace App\Providers; +use App\Services\Roles\Admin; +use App\Services\Roles\Developer; +use App\Services\Roles\ProductOwner; +use App\Services\Roles\Role; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -13,7 +17,9 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - // + $this->app->singleton('roleService', function ($app) { + return new Role(); + }); } /** @@ -23,6 +29,9 @@ public function register() */ public function boot() { - // + app('roleService') + ->addRole(new Admin()) + ->addRole(new Developer()) + ->addRole(new ProductOwner()); } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 22b77e6..9eaf4fe 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,6 +2,10 @@ namespace App\Providers; +use App\Models\Project; +use App\Models\User; +use App\Policies\ProjectPolicy; +use App\Policies\UserPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; @@ -13,6 +17,8 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ + User::class => UserPolicy::class, + Project::class => ProjectPolicy::class // 'App\Models\Model' => 'App\Policies\ModelPolicy', ]; diff --git a/app/Services/Roles/Admin.php b/app/Services/Roles/Admin.php new file mode 100644 index 0000000..54a3729 --- /dev/null +++ b/app/Services/Roles/Admin.php @@ -0,0 +1,28 @@ +roles[] = $role; + $this->rolesWithPermissions[$role->value()] = $role->permissions(); + return $this; + } + + public function options(): array + { + return collect($this->roles)->map(fn ($role) => [ + 'label' => $role->label(), + 'value' => $role->value(), + ])->toArray(); + } + + public function can($role, $permission): bool + { + return in_array($permission, $this->rolesWithPermissions[$role]); + } +} diff --git a/app/Services/Users/CreateUser.php b/app/Services/Users/CreateUser.php new file mode 100644 index 0000000..4cc1d84 --- /dev/null +++ b/app/Services/Users/CreateUser.php @@ -0,0 +1,18 @@ + $createUserDto->username, + 'password' => Hash::make($createUserDto->password), + 'role' => $createUserDto->role, + ]); + } +} diff --git a/app/Services/Users/UpdateUserWithPatch.php b/app/Services/Users/UpdateUserWithPatch.php new file mode 100644 index 0000000..74c0ea9 --- /dev/null +++ b/app/Services/Users/UpdateUserWithPatch.php @@ -0,0 +1,17 @@ +toArray(), fn ($value) => $value); + + $user->update($sanitizedValue); + + return $user; + } +} diff --git a/app/Services/Users/UpdateUserWithPut.php b/app/Services/Users/UpdateUserWithPut.php new file mode 100644 index 0000000..bd86d58 --- /dev/null +++ b/app/Services/Users/UpdateUserWithPut.php @@ -0,0 +1,20 @@ +username = $createUserDto->username; + $user->password = Hash::make($createUserDto->password); + $user->role = $createUserDto->role; + $user->timestamps = false; + $user->save(); + + return $user; + } +} 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 621a24e..47d8a7f 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -14,13 +14,13 @@ class CreateUsersTable extends Migration public function up() { Schema::create('users', function (Blueprint $table) { - $table->id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); + $table->uuid('id')->primary(); + $table->string('username')->unique(); $table->string('password'); + $table->string('role'); $table->rememberToken(); $table->timestamps(); + $table->softDeletes(); }); } 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 4315e16..d24fe13 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 @@ -15,7 +15,7 @@ public function up() { Schema::create('personal_access_tokens', function (Blueprint $table) { $table->id(); - $table->morphs('tokenable'); + $table->uuidMorphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); diff --git a/database/migrations/2022_11_14_151028_create_projects_table.php b/database/migrations/2022_11_14_151028_create_projects_table.php new file mode 100644 index 0000000..afdb90b --- /dev/null +++ b/database/migrations/2022_11_14_151028_create_projects_table.php @@ -0,0 +1,32 @@ +uuid('id')->primary(); + $table->string('name')->unique()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('projects'); + } +} diff --git a/database/migrations/2022_11_14_151037_create_tasks_table.php b/database/migrations/2022_11_14_151037_create_tasks_table.php new file mode 100644 index 0000000..842f350 --- /dev/null +++ b/database/migrations/2022_11_14_151037_create_tasks_table.php @@ -0,0 +1,39 @@ +uuid('id')->primary(); + $table->foreignUuid('project_id')->index()->references('id')->on('projects'); + $table->foreignUuid('user_id')->index()->references('id')->on('users'); + $table->string('title')->index(); + $table->string('description'); + $table->string('status'); + $table->integer('priority')->default(1); + $table->integer('points')->default(1); + $table->date('dateline'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('tasks'); + } +} diff --git a/database/migrations/2022_11_14_154004_create_project_user_table.php b/database/migrations/2022_11_14_154004_create_project_user_table.php new file mode 100644 index 0000000..669446b --- /dev/null +++ b/database/migrations/2022_11_14_154004_create_project_user_table.php @@ -0,0 +1,31 @@ +foreignUuid('project_id')->index(); + $table->foreignUuid('user_id')->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('project_user'); + } +} diff --git a/docs/Accenture.postman_collection.json b/docs/Accenture.postman_collection.json new file mode 100644 index 0000000..fb512b7 --- /dev/null +++ b/docs/Accenture.postman_collection.json @@ -0,0 +1,481 @@ +{ + "info": { + "_postman_id": "9913414c-07cf-461f-9af9-c899e5969a10", + "name": "Accenture", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Users", + "item": [ + { + "name": "Get All Users", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "{{URL}}/users", + "host": [ + "{{URL}}" + ], + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Get User By ID", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{URL}}/users/97c350ee-f039-4c57-89ef-b091068b6c20", + "host": [ + "{{URL}}" + ], + "path": [ + "users", + "97c350ee-f039-4c57-89ef-b091068b6c20" + ] + } + }, + "response": [] + }, + { + "name": "Update User By ID (IDEMPOTENT)", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"Hairul Azreen\",\n \"password\": \"password\",\n \"password_confirmation\": \"password\",\n \"role\": \"product_owner\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/users/97c350ee-f039-4c57-89ef-b091068b6c20", + "host": [ + "{{URL}}" + ], + "path": [ + "users", + "97c350ee-f039-4c57-89ef-b091068b6c20" + ] + } + }, + "response": [] + }, + { + "name": "Update User By ID", + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"Q\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/users/97c350ee-f039-4c57-89ef-b091068b6c20", + "host": [ + "{{URL}}" + ], + "path": [ + "users", + "97c350ee-f039-4c57-89ef-b091068b6c20" + ] + } + }, + "response": [] + }, + { + "name": "Create User", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"Emma Maembong\",\n \"password\": \"password\",\n \"password_confirmation\": \"password\",\n \"role\": \"dev\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/users", + "host": [ + "{{URL}}" + ], + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Delete User By ID", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "{{URL}}/users/97c35152-87b5-4f22-aedf-b38463fb8317", + "host": [ + "{{URL}}" + ], + "path": [ + "users", + "97c35152-87b5-4f22-aedf-b38463fb8317" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Projects", + "item": [ + { + "name": "Create Project", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Project 1\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/projects", + "host": [ + "{{URL}}" + ], + "path": [ + "projects" + ] + } + }, + "response": [] + }, + { + "name": "Get All Projects", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "{{URL}}/projects", + "host": [ + "{{URL}}" + ], + "path": [ + "projects" + ] + } + }, + "response": [] + }, + { + "name": "Get Project By ID", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "{{URL}}/projects/97c35f83-888a-42b3-99ad-16bcf63fed1e", + "host": [ + "{{URL}}" + ], + "path": [ + "projects", + "97c35f83-888a-42b3-99ad-16bcf63fed1e" + ] + } + }, + "response": [] + }, + { + "name": "Update Project By ID [PUT]", + "request": { + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Project\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/projects/97c364cf-f37f-47cc-9d7e-194f3ba6331a", + "host": [ + "{{URL}}" + ], + "path": [ + "projects", + "97c364cf-f37f-47cc-9d7e-194f3ba6331a" + ] + } + }, + "response": [] + }, + { + "name": "Update Project By ID [PATCH]", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Project 2\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/projects/97c364cf-f37f-47cc-9d7e-194f3ba6331a", + "host": [ + "{{URL}}" + ], + "path": [ + "projects", + "97c364cf-f37f-47cc-9d7e-194f3ba6331a" + ] + } + }, + "response": [] + }, + { + "name": "Delete Project By ID", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "{{URL}}/projects/97c35f83-888a-42b3-99ad-16bcf63fed1e", + "host": [ + "{{URL}}" + ], + "path": [ + "projects", + "97c35f83-888a-42b3-99ad-16bcf63fed1e" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"Hairul Azreen\",\n \"password\": \"password\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/login", + "host": [ + "{{URL}}" + ], + "path": [ + "login" + ] + } + }, + "response": [] + }, + { + "name": "Register", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"Zikri Nawawi\",\n \"password\": \"password\",\n \"password_confirmation\": \"password\",\n \"role\": \"admin\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/register", + "host": [ + "{{URL}}" + ], + "path": [ + "register" + ] + } + }, + "response": [] + }, + { + "name": "Get Roles", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json", + "type": "default" + } + ], + "url": { + "raw": "{{URL}}/roles", + "host": [ + "{{URL}}" + ], + "path": [ + "roles" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "5|JbSYEJmcdfDE1tBkQpjNfmrqF9KcrpNJ41aLOLWb", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "URL", + "value": "https://acn-2.test/api/v1", + "type": "default" + }, + { + "key": "user_id", + "value": "97beaa6c-e17e-4c13-92e2-09620abb8cbe", + "type": "default" + } + ] +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index eb6fa48..5b45385 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,9 @@ name('login'); +Route::post('v1/register', [AuthController::class, 'register'])->name('register'); + +Route::prefix('v1')->middleware('auth:sanctum')->group(function () { + Route::prefix('users')->group(function () { + Route::get('', [UserController::class, 'index'])->middleware('can:view,App\Models\User'); + Route::post('', [UserController::class, 'store'])->middleware('can:create,App\Models\User'); + Route::get('/{user}', [UserController::class, 'show'])->middleware('can:view,user'); + Route::put('/{user}', [UserController::class, 'updateWithPut'])->middleware('can:update,user'); + Route::patch('/{user}', [UserController::class, 'updateWithPatch'])->middleware('can:update,user'); + Route::delete('/{user}', [UserController::class, 'destroy'])->middleware('can:delete,user'); + }); + + Route::prefix('projects')->group(function () { + Route::get('', [ProjectController::class, 'index']); + Route::post('', [ProjectController::class, 'store']); + Route::put('/{project}', [ProjectController::class, 'updateWithPut']); + Route::patch('/{project}', [ProjectController::class, 'updateWithPatch']); + Route::get('/{project}', [ProjectController::class, 'show']); + Route::delete('/{project}', [ProjectController::class, 'destroy']); + }); + + Route::get('roles', RoleController::class); +}); + Route::middleware('auth:sanctum')->get('/user', function (Request $request) { return $request->user(); });