diff --git a/app/Http/Controllers/V1/Users/AccountUserListController.php b/app/Http/Controllers/V1/Users/AccountUserListController.php new file mode 100644 index 0000000..58033a7 --- /dev/null +++ b/app/Http/Controllers/V1/Users/AccountUserListController.php @@ -0,0 +1,37 @@ +framework->auth()->user(); + if ($user->hasNotPermission(UserRoles::ADMIN)) { + abort(ResponseStatus::FORBIDDEN->value); + } + $accountEntity = $user->getAccount(); + + $users = $this->userRepository->accountUserList($accountEntity); + + return response()->json($users->paginated()); + } +} diff --git a/core/Application/User/Commons/Gateways/UserRepositoryInterface.php b/core/Application/User/Commons/Gateways/UserRepositoryInterface.php index 1a55969..283754f 100644 --- a/core/Application/User/Commons/Gateways/UserRepositoryInterface.php +++ b/core/Application/User/Commons/Gateways/UserRepositoryInterface.php @@ -2,6 +2,8 @@ namespace Core\Application\User\Commons\Gateways; +use Core\Domain\Collections\User\UserCollection; +use Core\Domain\Entities\Account\AccountEntity; use Core\Domain\Entities\User\UserEntity; interface UserRepositoryInterface @@ -15,4 +17,6 @@ public function findByEmail(string $email): ?UserEntity; public function existsEmail(string $email): bool; public function existsId(int $id): bool; + + public function accountUserList(AccountEntity $account): UserCollection; } diff --git a/core/Domain/Collections/User/UserCollection.php b/core/Domain/Collections/User/UserCollection.php new file mode 100644 index 0000000..e53cf90 --- /dev/null +++ b/core/Domain/Collections/User/UserCollection.php @@ -0,0 +1,36 @@ +items[] = $user; + return $this; + } + + public function toArray(): array + { + return array_map( + fn(UserEntity $user) => [ + 'uuid' => $user->getUuid(), + 'name' => $user->getName(), + 'email' => $user->getEmail(), + 'account' => [ + 'uuid' => $user->getAccount()->getUuid(), + 'name' => $user->getAccount()->getName(), + ], + 'birthday' => $user->getBirthday()->getTimestamp(), + 'role' => $user->getRoleName(), + ], + $this->items + ); + } +} diff --git a/core/Support/Collections/CollectionBase.php b/core/Support/Collections/CollectionBase.php new file mode 100644 index 0000000..3aa5706 --- /dev/null +++ b/core/Support/Collections/CollectionBase.php @@ -0,0 +1,48 @@ +items); + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + public function remove(int $index): void + { + unset($this->items[$index]); + } + + public function exists(int $index): bool + { + return isset($this->items[$index]); + } + + public function getItens(): array + { + return $this->items; + } + + /** + * @throws MentodMustBeImplementedException + */ + public function toArray(): array + { + throw new MentodMustBeImplementedException('Method toArray not implemented'); + } +} diff --git a/core/Support/Collections/Paginations/Simple/HasDefaultPagination.php b/core/Support/Collections/Paginations/Simple/HasDefaultPagination.php new file mode 100644 index 0000000..c953497 --- /dev/null +++ b/core/Support/Collections/Paginations/Simple/HasDefaultPagination.php @@ -0,0 +1,181 @@ + $this->getCurrentPage(), + 'data' => $this->toArray(), + 'first_page_url' => $this->getFirstPageUrl(), + 'from' => $this->getFrom(), + 'last_page' => $this->getLastPage(), + 'last_page_url' => $this->getLastPageUrl(), + 'links' => $this->getLinks(), + 'next_page_url' => $this->getNextPageUrl(), + 'path' => $this->getPath(), + 'per_page' => $this->getPerPage(), + 'prev_page_url' => $this->getPrevPageUrl(), + 'to' => $this->getTo(), + 'total' => $this->getTotal(), + ]; + } + + public function getCurrentPage(): int + { + return $this->currentPage; + } + + public function setCurrentPage(int $currentPage): self + { + $this->currentPage = $currentPage; + return $this; + } + + public function getFirstPageUrl(): ?string + { + return $this->firstPageUrl; + } + + public function setFirstPageUrl(?string $firstPageUrl): self + { + $this->firstPageUrl = $firstPageUrl; + return $this; + } + + public function getFrom(): int + { + return $this->from; + } + + public function setFrom(int $from): self + { + $this->from = $from; + return $this; + } + + public function getLastPage(): int + { + return $this->lastPage; + } + + public function setLastPage(int $lastPage): self + { + $this->lastPage = $lastPage; + return $this; + } + + public function getLastPageUrl(): ?string + { + return $this->lastPageUrl; + } + + public function setLastPageUrl(?string $lastPageUrl): self + { + $this->lastPageUrl = $lastPageUrl; + return $this; + } + + public function getLinks(): array + { + return $this->links; + } + + public function setLinks(array $links): self + { + $this->links = $links; + return $this; + } + + public function getNextPageUrl(): ?string + { + return $this->nextPageUrl; + } + + public function setNextPageUrl(?string $nextPageUrl): self + { + $this->nextPageUrl = $nextPageUrl; + return $this; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function setPath(?string $path): self + { + $this->path = $path; + return $this; + } + + public function getPerPage(): int + { + return $this->perPage; + } + + public function setPerPage(int $perPage): self + { + $this->perPage = $perPage; + return $this; + } + + public function getPrevPageUrl(): ?string + { + return $this->prevPageUrl; + } + + public function setPrevPageUrl(?string $prevPageUrl): self + { + $this->prevPageUrl = $prevPageUrl; + return $this; + } + + public function getTo(): int + { + return $this->to; + } + + public function setTo(int $to): self + { + $this->to = $to; + return $this; + } + + public function getTotal(): int + { + return $this->total; + } + + public function setTotal(int $total): self + { + $this->total = $total; + return $this; + } + +} \ No newline at end of file diff --git a/core/Support/Collections/Paginations/toArrayInterface.php b/core/Support/Collections/Paginations/toArrayInterface.php new file mode 100644 index 0000000..c07a19f --- /dev/null +++ b/core/Support/Collections/Paginations/toArrayInterface.php @@ -0,0 +1,8 @@ +where('id', $id)->first(); + $accountModel = Account::query() + ->select(['id', 'name', 'uuid']) + ->where('id', $id) + ->first(); if (!$accountModel) { return null; } @@ -20,13 +24,16 @@ public function findByid(int $id): ?AccountEntity return AccountEntity::forDetail( id: $accountModel->id, name: $accountModel->name, - uuid: $accountModel->uuid + uuid: Uuid::fromString($accountModel->uuid) ); } public function findByUuid(string $uuid): ?AccountEntity { - $accountModel = Account::query()->where('uuid', $uuid)->first(); + $accountModel = Account::query() + ->select(['id', 'name', 'uuid']) + ->where('uuid', $uuid) + ->first(); if (!$accountModel) { return null; } @@ -34,13 +41,14 @@ public function findByUuid(string $uuid): ?AccountEntity return AccountEntity::forDetail( id: $accountModel->id, name: $accountModel->name, - uuid: $accountModel->uuid + uuid: Uuid::fromString($accountModel->uuid) ); } public function findByAccessCode(string $code): ?AccountEntity { $accountJoin = AccountJoinCode::query() + ->select(['id', 'code', 'account_id', 'expired_at']) ->where('code', '=', $code) ->whereNull('user_id') ->with(['account:id,name,uuid']) @@ -57,7 +65,7 @@ public function findByAccessCode(string $code): ?AccountEntity $accountEntity = AccountEntity::forDetail( id: $accountJoin->account->id, name: $accountJoin->account->name, - uuid: $accountJoin->account->uuid, + uuid: Uuid::fromString($accountJoin->account->uuid), ); return $accountEntity->setJoinCodeEntity($accountJoinEntity); diff --git a/infra/Database/User/Repository/UserRepository.php b/infra/Database/User/Repository/UserRepository.php index 3b2493e..c7eee44 100644 --- a/infra/Database/User/Repository/UserRepository.php +++ b/infra/Database/User/Repository/UserRepository.php @@ -4,6 +4,7 @@ use App\Models\User; use Core\Application\User\Commons\Gateways\UserRepositoryInterface; +use Core\Domain\Collections\User\UserCollection; use Core\Domain\Entities\Account\AccountEntity; use Core\Domain\Entities\User\UserEntity; use Core\Support\Exceptions\InvalidEmailException; @@ -92,4 +93,47 @@ public function existsId(int $id): bool { return User::query()->where('id', '=', $id)->exists(); } + + public function accountUserList(AccountEntity $account): UserCollection + { + $userModels = User::query() + ->select(['id', 'name', 'email', 'birthday', 'uuid', 'account_id', 'role']) + ->where('account_id', '=', $account->getId()) + ->with('account') + ->paginate(); + + $userCollection = new UserCollection(); + foreach ($userModels->items() as $userModel) { + $userCollection->add( + UserEntity::forDetail( + id: $userModel->id, + name: $userModel->name, + email: $userModel->email, + uuid: FrameworkService::getInstance()->uuid()->uuidFromString($userModel->uuid), + account: AccountEntity::forDetail( + $userModel->account->id, + $userModel->account->name, + FrameworkService::getInstance()->uuid()->uuidFromString($userModel->account->uuid) + ), + birthday: new DateTime($userModel->birthday), + role: $userModel->role + ) + ); + } + + + return $userCollection + ->setCurrentPage($userModels->currentPage()) + ->setFirstPageUrl($userModels->firstItem()) + //->setFrom($userModels->from()) + ->setLastPage($userModels->lastPage()) + ->setLastPageUrl($userModels->lastPage()) + //->setLinks($userModels->links) + ->setNextPageUrl($userModels->nextPageUrl()) + ->setPath($userModels->path()) + ->setPerPage($userModels->perPage()) + ->setPrevPageUrl($userModels->previousPageUrl()) + //->setTo($userModels->to) + ->setTotal($userModels->total()); + } } diff --git a/routes/api.php b/routes/api.php index 3a1bcf2..296562c 100644 --- a/routes/api.php +++ b/routes/api.php @@ -4,6 +4,7 @@ use App\Http\Controllers\V1\User\CreateUserController; use App\Http\Controllers\V1\User\ShowUserController; use App\Http\Controllers\V1\User\UpdateUserController; +use App\Http\Controllers\V1\Users\AccountUserListController; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; @@ -25,6 +26,8 @@ Route::patch('/user/change-role/{uuid}', [ChangeUserRoleController::class, '__invoke']) ->whereUuid('uuid') ->name('api.v1.user.change-role'); + Route::get('/user/list', [AccountUserListController::class, '__invoke']) + ->name('api.v1.user.list'); }); }); diff --git a/tests/Feature/Persistence/User/CreateUserCommandTest.php b/tests/Feature/Persistence/User/CreateUserCommandTest.php index 25a9bb6..4e6b96c 100644 --- a/tests/Feature/Persistence/User/CreateUserCommandTest.php +++ b/tests/Feature/Persistence/User/CreateUserCommandTest.php @@ -8,6 +8,7 @@ use Core\Domain\Entities\User\UserEntity; use Core\Services\Framework\FrameworkContract; use Illuminate\Foundation\Testing\DatabaseMigrations; +use Ramsey\Uuid\Uuid; use Tests\TestCase; class CreateUserCommandTest extends TestCase @@ -26,7 +27,7 @@ public function test_deve_testar_create_de_um_usuario(): void account: AccountEntity::forDetail( id: $accountModel->id, name: $accountModel->name, - uuid: $accountModel->uuid + uuid: Uuid::fromString($accountModel->uuid) ), uuid: $this->app->make(FrameworkContract::class)::getInstance()->uuid()->uuid7Generate(), birthday: now()->subYears(18) diff --git a/tests/Integration/e2e/User/AccountUserListE2eTest.php b/tests/Integration/e2e/User/AccountUserListE2eTest.php new file mode 100644 index 0000000..94642dd --- /dev/null +++ b/tests/Integration/e2e/User/AccountUserListE2eTest.php @@ -0,0 +1,92 @@ +getJson(route('api.v1.user.list')); + + $response->assertStatus(ResponseStatus::OK->value); + $response->assertJsonStructure([ + 'current_page', + 'data' => [ + '*' => [ + 'uuid', + 'name', + 'email', + 'account' => [ + 'uuid', + 'name', + ], + 'birthday', + 'role', + ], + ], + 'first_page_url', + 'from', + 'last_page', + 'last_page_url', + 'links', + 'next_page_url', + 'path', + 'per_page', + 'prev_page_url', + 'to', + 'total', + ]); + } + + public function test_fail_case_user_list_with_unauthenticated_user_as_viewer() + { + Sanctum::actingAs( + User::factory()->create([ + 'role' => UserRoles::VIEWER, + ]), + ['*'] + ); + $response = $this->getJson(route('api.v1.user.list')); + + $response->assertStatus(ResponseStatus::FORBIDDEN->value); + } + + public function test_fail_case_user_list_with_unauthenticated_user_as_editor() + { + Sanctum::actingAs( + User::factory()->create([ + 'role' => UserRoles::EDITOR, + ]), + ['*'] + ); + $response = $this->getJson(route('api.v1.user.list')); + + $response->assertStatus(ResponseStatus::FORBIDDEN->value); + } + + protected function setUp(): void + { + parent::setUp(); + $this->user = User::factory()->create([ + 'role' => UserRoles::ADMIN, + ]); + Sanctum::actingAs( + $this->user, + ['*'] + ); + } +} diff --git a/tests/Unit/Account/AccountEntityTest.php b/tests/Unit/Account/AccountEntityTest.php index 5e5dba3..2721aca 100644 --- a/tests/Unit/Account/AccountEntityTest.php +++ b/tests/Unit/Account/AccountEntityTest.php @@ -5,6 +5,7 @@ use Core\Application\Account\Commons\Exceptions\AccountNameInvalidException; use Core\Domain\Entities\Account\AccountEntity; use Core\Support\Http\ResponseStatus; +use Ramsey\Uuid\Uuid; use Tests\TestCase; class AccountEntityTest extends TestCase @@ -14,7 +15,7 @@ public function test_create_account_for_create_name_is_valid() // Arrange and Act AccountEntity::forCreate( name: '1', - uuid: 'uuid' + uuid: Uuid::uuid7() ); // Assert @@ -30,7 +31,7 @@ public function test_create_account_for_create_name_is_invalid() // Arrange and Act AccountEntity::forCreate( name: '', - uuid: 'uuid' + uuid: Uuid::uuid7() ); } }