diff --git a/data/doctrine/fixtures/UserLoader.php b/data/doctrine/fixtures/UserLoader.php index 0c956927..af373ea8 100644 --- a/data/doctrine/fixtures/UserLoader.php +++ b/data/doctrine/fixtures/UserLoader.php @@ -34,7 +34,6 @@ public function load(ObjectManager $manager): void ->setIdentity('test@dotkernel.com') ->usePassword('dotkernel') ->setStatus(UserStatusEnum::Active) - ->setIsDeleted(false) ->setHash(User::generateHash()) ->addRole($guestRole) ->addRole($userRole); diff --git a/data/doctrine/migrations/Version20241030082958.php b/data/doctrine/migrations/Version20241030082958.php index 86ad9ea5..7342c67c 100644 --- a/data/doctrine/migrations/Version20241030082958.php +++ b/data/doctrine/migrations/Version20241030082958.php @@ -30,7 +30,7 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE oauth_clients (id INT UNSIGNED AUTO_INCREMENT NOT NULL, name VARCHAR(40) NOT NULL, secret VARCHAR(100) DEFAULT NULL, redirect VARCHAR(191) NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, isConfidential TINYINT(1) DEFAULT 0 NOT NULL, user_id BINARY(16) DEFAULT NULL, INDEX IDX_13CE8101A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE oauth_refresh_tokens (id INT UNSIGNED AUTO_INCREMENT NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expires_at DATETIME NOT NULL, access_token_id INT UNSIGNED DEFAULT NULL, INDEX IDX_5AB6872CCB2688 (access_token_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE oauth_scopes (id INT UNSIGNED AUTO_INCREMENT NOT NULL, scope VARCHAR(191) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user (uuid BINARY(16) NOT NULL, identity VARCHAR(191) NOT NULL, password VARCHAR(191) NOT NULL, status ENUM(\'active\', \'pending\') DEFAULT \'pending\' NOT NULL, isDeleted TINYINT(1) NOT NULL, hash VARCHAR(64) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); + $this->addSql('CREATE TABLE user (uuid BINARY(16) NOT NULL, identity VARCHAR(191) NOT NULL, password VARCHAR(191) NOT NULL, status ENUM(\'active\', \'pending\', \'deleted\') DEFAULT \'pending\' NOT NULL, hash VARCHAR(64) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE user_roles (userUuid BINARY(16) NOT NULL, roleUuid BINARY(16) NOT NULL, INDEX IDX_54FCD59FD73087E9 (userUuid), INDEX IDX_54FCD59F88446210 (roleUuid), PRIMARY KEY(userUuid, roleUuid)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE user_avatar (uuid BINARY(16) NOT NULL, name VARCHAR(191) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_73256912D73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); $this->addSql('CREATE TABLE user_detail (uuid BINARY(16) NOT NULL, firstName VARCHAR(191) DEFAULT NULL, lastName VARCHAR(191) DEFAULT NULL, email VARCHAR(191) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_4B5464AED73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); diff --git a/src/User/src/Entity/User.php b/src/User/src/Entity/User.php index 0a54feab..5caad643 100644 --- a/src/User/src/Entity/User.php +++ b/src/User/src/Entity/User.php @@ -51,9 +51,6 @@ class User extends AbstractEntity implements UserEntityInterface #[ORM\Column(type: 'user_status_enum', options: ['default' => UserStatusEnum::Pending])] protected UserStatusEnum $status = UserStatusEnum::Pending; - #[ORM\Column(name: "isDeleted", type: "boolean")] - protected bool $isDeleted = false; - #[ORM\Column(name: "hash", type: "string", length: 64, unique: true)] protected string $hash; @@ -103,18 +100,6 @@ public function setStatus(UserStatusEnum $status): self return $this; } - public function isDeleted(): bool - { - return $this->isDeleted; - } - - public function setIsDeleted(bool $isDeleted): self - { - $this->isDeleted = $isDeleted; - - return $this; - } - public function getHash(): string { return $this->hash; @@ -273,11 +258,9 @@ public function isPending(): bool return $this->status === UserStatusEnum::Pending; } - public function markAsDeleted(): self + public function isDeleted(): bool { - $this->isDeleted = true; - - return $this; + return $this->status === UserStatusEnum::Deleted; } public function renewHash(): self @@ -311,7 +294,6 @@ public function getArrayCopy(): array 'hash' => $this->getHash(), 'identity' => $this->getIdentity(), 'status' => $this->getStatus(), - 'isDeleted' => $this->isDeleted(), 'avatar' => $this->getAvatar()?->getArrayCopy(), 'detail' => $this->getDetail()->getArrayCopy(), 'roles' => $this->getRoles()->map(function (UserRole $userRole) { diff --git a/src/User/src/Enum/UserStatusEnum.php b/src/User/src/Enum/UserStatusEnum.php index a2a30afa..a3863515 100644 --- a/src/User/src/Enum/UserStatusEnum.php +++ b/src/User/src/Enum/UserStatusEnum.php @@ -8,4 +8,5 @@ enum UserStatusEnum: string { case Active = 'active'; case Pending = 'pending'; + case Deleted = 'deleted'; } diff --git a/src/User/src/OpenAPI.php b/src/User/src/OpenAPI.php index 25390d2f..5b5ad2f9 100644 --- a/src/User/src/OpenAPI.php +++ b/src/User/src/OpenAPI.php @@ -1067,7 +1067,6 @@ new OA\Property(property: 'hash', type: 'string'), new OA\Property(property: 'identity', type: 'string'), new OA\Property(property: 'status', type: 'string', example: UserStatusEnum::Active), - new OA\Property(property: 'isDeleted', type: 'boolean', example: false), new OA\Property(property: 'avatar', ref: '#/components/schemas/UserAvatar', nullable: true), new OA\Property(property: 'detail', ref: '#/components/schemas/UserDetail'), new OA\Property( diff --git a/src/User/src/Repository/UserRepository.php b/src/User/src/Repository/UserRepository.php index 9bee50ad..f1590bdd 100644 --- a/src/User/src/Repository/UserRepository.php +++ b/src/User/src/Repository/UserRepository.php @@ -52,17 +52,6 @@ public function getUsers(array $filters = []): UserCollection $qb->andWhere('user.status = :status')->setParameter('status', $filters['status']); } - if (isset($filters['deleted'])) { - switch ($filters['deleted']) { - case 'true': - $qb->andWhere('user.isDeleted = :isDeleted')->setParameter('isDeleted', true); - break; - case 'false': - $qb->andWhere('user.isDeleted = :isDeleted')->setParameter('isDeleted', false); - break; - } - } - if (! empty($filters['search'])) { $qb->andWhere( $qb->expr()->orX( @@ -78,6 +67,8 @@ public function getUsers(array $filters = []): UserCollection $qb->andWhere('roles.name = :role')->setParameter('role', $filters['role']); } + //ignore deleted users + $qb->andWhere('user.status != :status')->setParameter('status', UserStatusEnum::Deleted); $qb->getQuery()->useQueryCache(true); return new UserCollection($qb, false); @@ -115,8 +106,9 @@ public function getUserEntityByUserCredentials( $qb->select(['u.password', 'u.status']) ->from(User::class, 'u') ->andWhere('u.identity = :identity') - ->andWhere('u.isDeleted = 0') - ->setParameter('identity', $username); + ->andWhere('u.status != :status') + ->setParameter('identity', $username) + ->setParameter('status', UserStatusEnum::Deleted); break; default: throw new OAuthServerException(Message::INVALID_CLIENT_ID, 6, 'invalid_client', 401); diff --git a/src/User/src/Service/UserService.php b/src/User/src/Service/UserService.php index 2fbb5d00..f6c2d312 100644 --- a/src/User/src/Service/UserService.php +++ b/src/User/src/Service/UserService.php @@ -115,7 +115,7 @@ public function deleteUser(User $user): User { $this->revokeTokens($user); - return $this->anonymizeUser($user->markAsDeleted()); + return $this->anonymizeUser($user->setStatus(UserStatusEnum::Deleted)); } /** @@ -198,7 +198,7 @@ public function findResetPasswordByHash(?string $hash): UserResetPassword public function findByEmail(string $email): User { $user = $this->userDetailRepository->findOneBy(['email' => $email])?->getUser(); - if (! $user instanceof User) { + if (! $user instanceof User || $user->isDeleted()) { throw new NotFoundException(Message::USER_NOT_FOUND); } @@ -219,7 +219,7 @@ public function findByIdentity(string $identity): ?User public function findOneBy(array $params = []): User { $user = $this->userRepository->findOneBy($params); - if (! $user instanceof User) { + if (! $user instanceof User || $user->isDeleted()) { throw new NotFoundException(Message::USER_NOT_FOUND); } @@ -370,10 +370,6 @@ public function updateUser(User $user, array $data = []): User $user->setStatus($data['status']); } - if (isset($data['isDeleted'])) { - $user->setIsDeleted($data['isDeleted']); - } - if (isset($data['hash'])) { $user->setHash($data['hash']); } diff --git a/test/Functional/AdminTest.php b/test/Functional/AdminTest.php index e946e2e9..a00f7b8c 100644 --- a/test/Functional/AdminTest.php +++ b/test/Functional/AdminTest.php @@ -401,7 +401,6 @@ public function testAdminCanCreateUserAccount(): void $this->assertArrayHasKey('hash', $data); $this->assertArrayHasKey('identity', $data); $this->assertArrayHasKey('status', $data); - $this->assertArrayHasKey('isDeleted', $data); $this->assertArrayHasKey('avatar', $data); $this->assertArrayHasKey('detail', $data); $this->assertArrayHasKey('roles', $data); @@ -409,7 +408,6 @@ public function testAdminCanCreateUserAccount(): void $this->assertNotEmpty($data['hash']); $this->assertSame($userData['identity'], $data['identity']); $this->assertSame(UserStatusEnum::Pending->value, $data['status']); - $this->assertFalse($data['isDeleted']); $this->assertEmpty($data['avatar']); $this->assertEmpty($data['resetPasswords']); $this->assertArrayHasKey('firstName', $data['detail']); diff --git a/test/Functional/UserTest.php b/test/Functional/UserTest.php index 75d216ae..b74fff00 100644 --- a/test/Functional/UserTest.php +++ b/test/Functional/UserTest.php @@ -108,7 +108,6 @@ public function testRegisterAccount(): void $this->assertSame($user['identity'], $data['identity']); $this->assertSame(UserStatusEnum::Pending->value, $data['status']); - $this->assertFalse($data['isDeleted']); $this->assertArrayHasKey('detail', $data); $this->assertArrayHasKey('email', $data['detail']); $this->assertArrayHasKey('firstName', $data['detail']); diff --git a/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php b/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php index 5d6cc3fe..cc6dfd11 100644 --- a/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php +++ b/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php @@ -13,6 +13,7 @@ use Api\App\UserIdentity; use Api\User\Entity\User; use Api\User\Entity\UserRole; +use Api\User\Enum\UserStatusEnum; use Api\User\Repository\UserRepository; use Fig\Http\Message\StatusCodeInterface; use Laminas\Diactoros\ServerRequest; @@ -108,7 +109,7 @@ public function testAuthorizationInactiveUser(): void public function testAuthorizationUserNotFoundOrDeleted(): void { - $user = (new User())->markAsDeleted(); + $user = (new User())->setStatus(UserStatusEnum::Deleted); $this->userRepository->method('findOneBy')->willReturn($user); $this->authorization->method('isGranted')->willReturn(false); diff --git a/test/Unit/User/Service/UserServiceTest.php b/test/Unit/User/Service/UserServiceTest.php index e07b8c57..5d1228e9 100644 --- a/test/Unit/User/Service/UserServiceTest.php +++ b/test/Unit/User/Service/UserServiceTest.php @@ -147,7 +147,6 @@ public function testCreateUser(): void $this->assertSame($data['detail']['lastName'], $user->getDetail()->getLastName()); $this->assertSame($data['detail']['email'], $user->getDetail()->getEmail()); $this->assertSame(UserStatusEnum::Pending, $user->getStatus()); - $this->assertFalse($user->isDeleted()); $this->assertFalse($user->isActive()); }