From 8bb8306721f107d3987ffd717acb5046a04639df Mon Sep 17 00:00:00 2001 From: Michal Cieslar Date: Wed, 28 Feb 2024 23:21:20 +0100 Subject: [PATCH 1/2] remove domain actions & refactor --- .../backend/src/common/types/databaseTable.ts | 3 +- .../src/libs/database/types/queryBuilder.ts | 5 - ...urceCommandHandlerImpl.integration.test.ts | 13 +- ...sourceQueryHandlerImpl.integration.test.ts | 15 +- ...tadataQueryHandlerImpl.integration.test.ts | 11 +- .../createUserCommandHandlerImpl.ts | 10 +- ...cessCommandHandlerImpl.integration.test.ts | 11 +- .../grantBucketAccessCommandHandlerImpl.ts | 12 +- ...UserCommandHandlerImpl.integration.test.ts | 6 - .../loginUserCommandHandlerImpl.ts | 12 - ...UserCommandHandlerImpl.integration.test.ts | 66 ----- .../logoutUserCommandHandlerImpl.ts | 19 -- ...kensCommandHandlerImpl.integration.test.ts | 74 ----- .../refreshUserTokensCommandHandlerImpl.ts | 10 +- ...cessCommandHandlerImpl.integration.test.ts | 26 +- .../revokeBucketAccessCommandHandlerImpl.ts | 13 +- ...ucketsQueryHandlerImpl.integration.test.ts | 33 +-- .../findUserBucketsQueryHandlerImpl.ts | 6 +- .../entities/refreshToken/refreshToken.ts | 41 --- .../createRefreshTokenDomainAction.ts | 9 - .../grantBucketAccessDomainAction.ts | 8 - .../revokeBucketAccessDomainAction.ts | 8 - .../user/domainActions/userDomainAction.ts | 8 - .../domainActions/userDomainActionType.ts | 5 - .../userModule/domain/entities/user/user.ts | 83 ++---- .../domain/entities/userTokens/userTokens.ts | 3 - .../userBucketRepository.ts | 20 ++ .../userRepository/userRepository.ts | 33 +-- .../m3CreateRefreshTokenTableMigration.ts | 37 --- ...ts => m3CreateUserBucketTableMigration.ts} | 4 +- .../blacklistTokenTable.ts | 8 +- .../refreshTokenRawEntity.ts | 6 - .../refreshTokenTable/refreshTokenTable.ts | 12 - .../tables/userBucketTable/userBucketTable.ts | 8 +- .../tables/userTable/userRawEntity.ts | 4 +- .../tables/userTable/userTable.ts | 9 +- .../userDatabaseMigrationSource.ts | 6 +- .../blacklistTokenRepositoryImpl.ts | 18 +- .../userBucketMapper/userBucketMapper.ts | 0 .../userBucketMapper/userBucketMapperImpl.ts | 0 .../userBucketMapperImpl.unit.test.ts | 6 +- ...erBucketRepositoryImpl.integration.test.ts | 112 ++++++++ .../userBucketRepositoryImpl.ts | 82 ++++++ .../userMapper/userMapperImpl.ts | 4 +- .../userMapper/userMapperImpl.unit.test.ts | 12 +- .../userRepositoryImpl.integration.test.ts | 202 +------------- .../userRepository/userRepositoryImpl.ts | 257 +----------------- .../backend/src/modules/userModule/symbols.ts | 1 + .../refreshTokenTestFactory.ts | 15 - .../userBucketEntityTestFactory.ts | 14 - .../userBucketTestFactory.ts | 10 + .../userEntityTestFactory.ts | 16 -- .../userTestFactory/userTestFactory.ts | 11 + .../blacklistTokenTestUtils.ts | 27 +- .../userBucketTestUtils.ts | 67 +++++ .../utils/userTestUtils/userTestUtils.ts | 110 +------- .../src/modules/userModule/userModule.ts | 21 +- apps/backend/tests/container/symbols.ts | 2 +- apps/backend/tests/container/testContainer.ts | 6 + docker-compose.dev.yml => docker-compose.yml | 0 60 files changed, 507 insertions(+), 1143 deletions(-) delete mode 100644 apps/backend/src/libs/database/types/queryBuilder.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/refreshToken/refreshToken.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/user/domainActions/createRefreshTokenDomainAction.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/user/domainActions/grantBucketAccessDomainAction.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/user/domainActions/revokeBucketAccessDomainAction.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainAction.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainActionType.ts delete mode 100644 apps/backend/src/modules/userModule/domain/entities/userTokens/userTokens.ts create mode 100644 apps/backend/src/modules/userModule/domain/repositories/userBucketRepository/userBucketRepository.ts delete mode 100644 apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateRefreshTokenTableMigration.ts rename apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/{m4CreateUserBucketTableMigration.ts => m3CreateUserBucketTableMigration.ts} (85%) delete mode 100644 apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenRawEntity.ts delete mode 100644 apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenTable.ts rename apps/backend/src/modules/userModule/infrastructure/repositories/{userRepository => userBucketRepository}/userBucketMapper/userBucketMapper.ts (100%) rename apps/backend/src/modules/userModule/infrastructure/repositories/{userRepository => userBucketRepository}/userBucketMapper/userBucketMapperImpl.ts (100%) rename apps/backend/src/modules/userModule/infrastructure/repositories/{userRepository => userBucketRepository}/userBucketMapper/userBucketMapperImpl.unit.test.ts (65%) create mode 100644 apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.integration.test.ts create mode 100644 apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.ts delete mode 100644 apps/backend/src/modules/userModule/tests/factories/refreshTokenTestFactory/refreshTokenTestFactory.ts delete mode 100644 apps/backend/src/modules/userModule/tests/factories/userBucketEntityTestFactory/userBucketEntityTestFactory.ts delete mode 100644 apps/backend/src/modules/userModule/tests/factories/userEntityTestFactory/userEntityTestFactory.ts create mode 100644 apps/backend/src/modules/userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.ts rename docker-compose.dev.yml => docker-compose.yml (100%) diff --git a/apps/backend/src/common/types/databaseTable.ts b/apps/backend/src/common/types/databaseTable.ts index 5b5cc11..92043c2 100644 --- a/apps/backend/src/common/types/databaseTable.ts +++ b/apps/backend/src/common/types/databaseTable.ts @@ -1,4 +1,3 @@ -export interface DatabaseTable { +export interface DatabaseTable { readonly name: string; - readonly columns: Record; } diff --git a/apps/backend/src/libs/database/types/queryBuilder.ts b/apps/backend/src/libs/database/types/queryBuilder.ts deleted file mode 100644 index 7a84860..0000000 --- a/apps/backend/src/libs/database/types/queryBuilder.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { type Knex } from 'knex'; - -export type QueryBuilder = Knex.QueryBuilder; diff --git a/apps/backend/src/modules/resourceModule/application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandlerImpl.integration.test.ts b/apps/backend/src/modules/resourceModule/application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandlerImpl.integration.test.ts index b176b27..c611a4b 100644 --- a/apps/backend/src/modules/resourceModule/application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/resourceModule/application/commandHandlers/deleteResourceCommandHandler/deleteResourceCommandHandlerImpl.integration.test.ts @@ -11,6 +11,7 @@ import { ResourceNotFoundError } from '../../../../../common/errors/common/resou import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; import { coreSymbols } from '../../../../../core/symbols.js'; import { type DependencyInjectionContainer } from '../../../../../libs/dependencyInjection/dependencyInjectionContainer.js'; +import { type UserBucketTestUtils } from '../../../../userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.js'; import { type UserTestUtils } from '../../../../userModule/tests/utils/userTestUtils/userTestUtils.js'; import { symbols } from '../../../symbols.js'; import { type S3TestUtils } from '../../../tests/utils/s3TestUtils.js'; @@ -26,6 +27,8 @@ describe('DeleteResourceCommandHandlerImpl', () => { let userTestUtils: UserTestUtils; + let userBucketTestUtils: UserBucketTestUtils; + const resourcesDirectory = path.resolve(__dirname, '../../../../../../../../resources'); const sampleFileName = 'sample_video1.mp4'; @@ -41,16 +44,22 @@ describe('DeleteResourceCommandHandlerImpl', () => { userTestUtils = container.get(testSymbols.userTestUtils); + userBucketTestUtils = container.get(testSymbols.userBucketTestUtils); + s3TestUtils = container.get(testSymbols.s3TestUtils); await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await s3TestUtils.createBucket(bucketName); }); afterEach(async () => { await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await sqliteDatabaseClient.destroy(); await s3TestUtils.deleteBucket(bucketName); @@ -97,7 +106,7 @@ describe('DeleteResourceCommandHandlerImpl', () => { const user = await userTestUtils.createAndPersist(); - await userTestUtils.createAndPersistUserBucket({ + await userBucketTestUtils.createAndPersist({ input: { userId: user.id, bucketName, @@ -122,7 +131,7 @@ describe('DeleteResourceCommandHandlerImpl', () => { it('deletes a resource', async () => { const user = await userTestUtils.createAndPersist(); - await userTestUtils.createAndPersistUserBucket({ + await userBucketTestUtils.createAndPersist({ input: { userId: user.id, bucketName, diff --git a/apps/backend/src/modules/resourceModule/application/queryHandlers/downloadResourceQueryHandler/downloadResourceQueryHandlerImpl.integration.test.ts b/apps/backend/src/modules/resourceModule/application/queryHandlers/downloadResourceQueryHandler/downloadResourceQueryHandlerImpl.integration.test.ts index 9e95d21..58f6d7e 100644 --- a/apps/backend/src/modules/resourceModule/application/queryHandlers/downloadResourceQueryHandler/downloadResourceQueryHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/resourceModule/application/queryHandlers/downloadResourceQueryHandler/downloadResourceQueryHandlerImpl.integration.test.ts @@ -10,6 +10,7 @@ import { ResourceNotFoundError } from '../../../../../common/errors/common/resou import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; import { coreSymbols } from '../../../../../core/symbols.js'; import { type DependencyInjectionContainer } from '../../../../../libs/dependencyInjection/dependencyInjectionContainer.js'; +import { type UserBucketTestUtils } from '../../../../userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.js'; import { type UserTestUtils } from '../../../../userModule/tests/utils/userTestUtils/userTestUtils.js'; import { symbols } from '../../../symbols.js'; import { type S3TestUtils } from '../../../tests/utils/s3TestUtils.js'; @@ -25,6 +26,8 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { let userTestUtils: UserTestUtils; + let userBucketTestUtils: UserBucketTestUtils; + const resourcesDirectory = path.resolve(__dirname, '../../../../../../../../resources'); const sampleFileName1 = 'sample_video1.mp4'; @@ -40,16 +43,22 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { userTestUtils = container.get(testSymbols.userTestUtils); + userBucketTestUtils = container.get(testSymbols.userBucketTestUtils); + s3TestUtils = container.get(testSymbols.s3TestUtils); await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await s3TestUtils.createBucket(bucketName); }); afterEach(async () => { await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await sqliteDatabaseClient.destroy(); await s3TestUtils.deleteBucket(bucketName); @@ -94,7 +103,7 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { it('throws an error - when Resource does not exist', async () => { const user = await userTestUtils.createAndPersist(); - await userTestUtils.createAndPersistUserBucket({ + await userBucketTestUtils.createAndPersist({ input: { userId: user.id, bucketName, @@ -119,7 +128,7 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { it('downloads a Resource', async () => { const user = await userTestUtils.createAndPersist(); - await userTestUtils.createAndPersistUserBucket({ + await userBucketTestUtils.createAndPersist({ input: { userId: user.id, bucketName, @@ -134,8 +143,6 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { bucketName, }); - // TODO: validate data - expect(resource).toEqual({ name: sampleFileName1, updatedAt: expect.any(Date), diff --git a/apps/backend/src/modules/resourceModule/application/queryHandlers/findResourcesMetadataQueryHandler/findResourcesMetadataQueryHandlerImpl.integration.test.ts b/apps/backend/src/modules/resourceModule/application/queryHandlers/findResourcesMetadataQueryHandler/findResourcesMetadataQueryHandlerImpl.integration.test.ts index 4b43111..e29adeb 100644 --- a/apps/backend/src/modules/resourceModule/application/queryHandlers/findResourcesMetadataQueryHandler/findResourcesMetadataQueryHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/resourceModule/application/queryHandlers/findResourcesMetadataQueryHandler/findResourcesMetadataQueryHandlerImpl.integration.test.ts @@ -10,6 +10,7 @@ import { ResourceNotFoundError } from '../../../../../common/errors/common/resou import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; import { coreSymbols } from '../../../../../core/symbols.js'; import { type DependencyInjectionContainer } from '../../../../../libs/dependencyInjection/dependencyInjectionContainer.js'; +import { type UserBucketTestUtils } from '../../../../userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.js'; import { type UserTestUtils } from '../../../../userModule/tests/utils/userTestUtils/userTestUtils.js'; import { symbols } from '../../../symbols.js'; import { type S3TestUtils } from '../../../tests/utils/s3TestUtils.js'; @@ -25,6 +26,8 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { let userTestUtils: UserTestUtils; + let userBucketTestUtils: UserBucketTestUtils; + const resourcesDirectory = path.resolve(__dirname, '../../../../../../../../resources'); const sampleFileName1 = 'sample_video1.mp4'; @@ -42,16 +45,22 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { userTestUtils = container.get(testSymbols.userTestUtils); + userBucketTestUtils = container.get(testSymbols.userBucketTestUtils); + s3TestUtils = container.get(testSymbols.s3TestUtils); await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await s3TestUtils.createBucket(bucketName); }); afterEach(async () => { await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await sqliteDatabaseClient.destroy(); await s3TestUtils.deleteBucket(bucketName); @@ -98,7 +107,7 @@ describe('FindResourcesMetadataQueryHandlerImpl', () => { it('finds resources metadata', async () => { const user = await userTestUtils.createAndPersist(); - await userTestUtils.createAndPersistUserBucket({ + await userBucketTestUtils.createAndPersist({ input: { userId: user.id, bucketName, diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/createUserCommandHandler/createUserCommandHandlerImpl.ts b/apps/backend/src/modules/userModule/application/commandHandlers/createUserCommandHandler/createUserCommandHandlerImpl.ts index 2b168cc..a44377a 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/createUserCommandHandler/createUserCommandHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/createUserCommandHandler/createUserCommandHandlerImpl.ts @@ -42,10 +42,12 @@ export class CrateUserCommandHandlerImpl implements CreateUserCommandHandler { const hashedPassword = await this.hashService.hash({ plainData: password }); - const user = await this.userRepository.createUser({ - email, - password: hashedPassword, - role: UserRole.user, + const user = await this.userRepository.saveUser({ + user: { + email, + password: hashedPassword, + role: UserRole.user, + }, }); this.loggerService.debug({ diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.integration.test.ts b/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.integration.test.ts index 45706fb..e228608 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.integration.test.ts @@ -9,6 +9,7 @@ import { OperationNotValidError } from '../../../../../common/errors/common/oper import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; import { coreSymbols } from '../../../../../core/symbols.js'; import { symbols } from '../../../symbols.js'; +import { type UserBucketTestUtils } from '../../../tests/utils/userBucketTestUtils/userBucketTestUtils.js'; import { type UserTestUtils } from '../../../tests/utils/userTestUtils/userTestUtils.js'; describe('GrantBucketAccessCommandHandlerImpl', () => { @@ -18,6 +19,8 @@ describe('GrantBucketAccessCommandHandlerImpl', () => { let userTestUtils: UserTestUtils; + let userBucketTestUtils: UserBucketTestUtils; + beforeEach(async () => { const container = TestContainer.create(); @@ -27,12 +30,18 @@ describe('GrantBucketAccessCommandHandlerImpl', () => { userTestUtils = container.get(testSymbols.userTestUtils); + userBucketTestUtils = container.get(testSymbols.userBucketTestUtils); + await userTestUtils.truncate(); + + await userBucketTestUtils.truncate(); }); afterEach(async () => { await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await sqliteDatabaseClient.destroy(); }); @@ -46,7 +55,7 @@ describe('GrantBucketAccessCommandHandlerImpl', () => { bucketName, }); - const userBuckets = await userTestUtils.findBucketsByUserId({ userId: user.id }); + const userBuckets = await userBucketTestUtils.findUserBuckets({ userId: user.id }); expect(userBuckets.find((userBucket) => userBucket.bucketName === bucketName)).toBeDefined(); }); diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.ts b/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.ts index dc078a2..0f86dd7 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/grantBucketAccessCommandHandler/grantBucketAccessCommandHandlerImpl.ts @@ -4,11 +4,13 @@ import { } from './grantBucketAccessCommandHandler.js'; import { OperationNotValidError } from '../../../../../common/errors/common/operationNotValidError.js'; import { type LoggerService } from '../../../../../libs/logger/services/loggerService/loggerService.js'; +import { type UserBucketRepository } from '../../../domain/repositories/userBucketRepository/userBucketRepository.js'; import { type UserRepository } from '../../../domain/repositories/userRepository/userRepository.js'; export class GrantBucketAccessCommandHandlerImpl implements GrantBucketAccessCommandHandler { public constructor( private readonly userRepository: UserRepository, + private readonly userBucketRepository: UserBucketRepository, private readonly loggerService: LoggerService, ) {} @@ -30,7 +32,7 @@ export class GrantBucketAccessCommandHandlerImpl implements GrantBucketAccessCom }); } - const existingBuckets = await this.userRepository.findUserBuckets({ userId }); + const existingBuckets = await this.userBucketRepository.findUserBuckets({ userId }); if (existingBuckets.find((userBucket) => userBucket.getBucketName() === bucketName)) { this.loggerService.debug({ @@ -42,13 +44,9 @@ export class GrantBucketAccessCommandHandlerImpl implements GrantBucketAccessCom return; } - existingUser.addGrantBucketAccessAction({ + await this.userBucketRepository.createUserBucket({ bucketName, - }); - - await this.userRepository.updateUser({ - id: userId, - domainActions: existingUser.getDomainActions(), + userId, }); this.loggerService.debug({ diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.integration.test.ts b/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.integration.test.ts index ff21ee8..6757efc 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.integration.test.ts @@ -80,12 +80,6 @@ describe('LoginUserCommandHandler', () => { expect(refreshTokenPayload['userId']).toBe(createdUser.getId()); - const userTokens = await userTestUtils.findTokensByUserId({ - userId: createdUser.getId(), - }); - - expect(userTokens.refreshTokens.includes(refreshToken)).toBe(true); - expect(accessTokenExpiresIn).toBe(configProvider.getAccessTokenExpiresIn()); }); diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.ts b/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.ts index 400693e..e77930f 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/loginUserCommandHandler/loginUserCommandHandlerImpl.ts @@ -70,18 +70,6 @@ export class LoginUserCommandHandlerImpl implements LoginUserCommandHandler { expiresIn: refreshTokenExpiresIn, }); - const expiresAt = new Date(Date.now() + refreshTokenExpiresIn * 1000); - - user.addCreateRefreshTokenAction({ - token: refreshToken, - expiresAt, - }); - - await this.userRepository.updateUser({ - id: user.getId(), - domainActions: user.getDomainActions(), - }); - this.loggerService.debug({ message: 'User logged in.', email, diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.integration.test.ts b/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.integration.test.ts index 2acc2ca..33523c1 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.integration.test.ts @@ -55,13 +55,6 @@ describe('LogoutUserCommandHandlerImpl', () => { const user = await userTestUtils.createAndPersist(); - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken, - }, - }); - await commandHandler.execute({ userId: user.id, refreshToken, @@ -96,63 +89,4 @@ describe('LogoutUserCommandHandlerImpl', () => { }, }); }); - - it('throws an error - when UserTokens not found', async () => { - const refreshToken = tokenService.createToken({ - data: { valid: 'true' }, - expiresIn: Generator.number(10000, 100000), - }); - - const user = await userTestUtils.createAndPersist(); - - await expect( - async () => - await commandHandler.execute({ - userId: user.id, - refreshToken, - }), - ).toThrowErrorInstance({ - instance: OperationNotValidError, - context: { - reason: 'User tokens not found.', - userId: user.id, - }, - }); - }); - - it('throws an error - when UserTokens were found but refreshToken is different', async () => { - const refreshToken = tokenService.createToken({ - data: { valid: 'true' }, - expiresIn: Generator.number(10000, 100000), - }); - - const user = await userTestUtils.createAndPersist(); - - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken, - }, - }); - - const invalidRefreshToken = tokenService.createToken({ - data: { invalid: 'true' }, - expiresIn: Generator.number(), - }); - - await expect( - async () => - await commandHandler.execute({ - userId: user.id, - refreshToken: invalidRefreshToken, - }), - ).toThrowErrorInstance({ - instance: OperationNotValidError, - context: { - reason: 'Refresh token is not valid.', - userId: user.id, - refreshToken: invalidRefreshToken, - }, - }); - }); }); diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.ts b/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.ts index e848361..93b49de 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/logoutUserCommandHandler/logoutUserCommandHandlerImpl.ts @@ -48,25 +48,6 @@ export class LogoutUserCommandHandlerImpl implements LogoutUserCommandHandler { }); } - const userTokens = await this.userRepository.findUserTokens({ - userId, - }); - - if (!userTokens) { - throw new OperationNotValidError({ - reason: 'User tokens not found.', - userId, - }); - } - - if (!userTokens.refreshTokens.includes(refreshToken)) { - throw new OperationNotValidError({ - reason: 'Refresh token is not valid.', - userId, - refreshToken, - }); - } - const { expiresAt } = this.tokenService.decodeToken({ token: refreshToken, }); diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.integration.test.ts b/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.integration.test.ts index e6ae59e..2de0403 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.integration.test.ts @@ -67,13 +67,6 @@ describe('RefreshUserTokensCommandHandler', () => { expiresIn: Generator.number(10000, 100000), }); - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken, - }, - }); - const result = await refreshUserTokensCommandHandler.execute({ refreshToken, }); @@ -110,72 +103,12 @@ describe('RefreshUserTokensCommandHandler', () => { }); }); - it('throws an error if User tokens do not exist', async () => { - const user = await userTestUtils.createAndPersist(); - - const refreshToken = tokenService.createToken({ - data: { userId: user.id }, - expiresIn: Generator.number(10000, 100000), - }); - - await expect(async () => - refreshUserTokensCommandHandler.execute({ - refreshToken, - }), - ).toThrowErrorInstance({ - instance: ResourceNotFoundError, - context: { - name: 'UserTokens', - userId: user.id, - }, - }); - }); - - it('throws an error if refresh token userId does not match userId from User tokens', async () => { - const user = await userTestUtils.createAndPersist(); - - const refreshToken = tokenService.createToken({ - data: { userId: user.id }, - expiresIn: Generator.number(10000, 100000), - }); - - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: tokenService.createToken({ - data: { userId: user.id }, - expiresIn: Generator.number(10000, 100000), - }), - }, - }); - - await expect(async () => - refreshUserTokensCommandHandler.execute({ - refreshToken, - }), - ).toThrowErrorInstance({ - instance: OperationNotValidError, - context: { - reason: 'Refresh token does not match the one from User tokens.', - }, - }); - }); - it('throws an error if refresh token does not contain userId', async () => { - const user = await userTestUtils.createAndPersist(); - const refreshToken = tokenService.createToken({ data: {}, expiresIn: Generator.number(10000, 100000), }); - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken, - }, - }); - await expect(async () => refreshUserTokensCommandHandler.execute({ refreshToken, @@ -196,13 +129,6 @@ describe('RefreshUserTokensCommandHandler', () => { expiresIn: Generator.number(10000, 100000), }); - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken, - }, - }); - await blacklistTokenTestUtils.createAndPersist({ input: { token: refreshToken } }); await expect(async () => diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.ts b/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.ts index e14f025..a6bcda3 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/refreshUserTokensCommandHandler/refreshUserTokensCommandHandlerImpl.ts @@ -8,6 +8,7 @@ import { ResourceNotFoundError } from '../../../../../common/errors/common/resou import { type LoggerService } from '../../../../../libs/logger/services/loggerService/loggerService.js'; import { type TokenService } from '../../../../authModule/application/services/tokenService/tokenService.js'; import { type BlacklistTokenRepository } from '../../../domain/repositories/blacklistTokenRepository/blacklistTokenRepository.js'; +import { type UserBucketRepository } from '../../../domain/repositories/userBucketRepository/userBucketRepository.js'; import { type UserRepository } from '../../../domain/repositories/userRepository/userRepository.js'; import { type UserModuleConfigProvider } from '../../../userModuleConfigProvider.js'; @@ -17,6 +18,7 @@ export class RefreshUserTokensCommandHandlerImpl implements RefreshUserTokensCom private readonly tokenService: TokenService, private readonly configProvider: UserModuleConfigProvider, private readonly userRepository: UserRepository, + private readonly userBucketRepository: UserBucketRepository, private readonly blacklistTokenRepository: BlacklistTokenRepository, ) {} @@ -59,7 +61,7 @@ export class RefreshUserTokensCommandHandlerImpl implements RefreshUserTokensCom }); } - const userTokens = await this.userRepository.findUserTokens({ userId }); + const userTokens = await this.userBucketRepository.findUserBuckets({ userId }); if (!userTokens) { throw new ResourceNotFoundError({ @@ -68,12 +70,6 @@ export class RefreshUserTokensCommandHandlerImpl implements RefreshUserTokensCom }); } - if (!userTokens.refreshTokens.includes(refreshToken)) { - throw new OperationNotValidError({ - reason: 'Refresh token does not match the one from User tokens.', - }); - } - const accessTokenExpiresIn = this.configProvider.getAccessTokenExpiresIn(); const accessToken = this.tokenService.createToken({ diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.integration.test.ts b/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.integration.test.ts index 2f46a5f..a334ef4 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.integration.test.ts @@ -9,6 +9,7 @@ import { OperationNotValidError } from '../../../../../common/errors/common/oper import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; import { coreSymbols } from '../../../../../core/symbols.js'; import { symbols } from '../../../symbols.js'; +import { type UserBucketTestUtils } from '../../../tests/utils/userBucketTestUtils/userBucketTestUtils.js'; import { type UserTestUtils } from '../../../tests/utils/userTestUtils/userTestUtils.js'; describe('RevokeBucketAccessCommandHandlerImpl', () => { @@ -18,44 +19,45 @@ describe('RevokeBucketAccessCommandHandlerImpl', () => { let userTestUtils: UserTestUtils; + let userBucketTestUtils: UserBucketTestUtils; + beforeEach(async () => { const container = TestContainer.create(); - commandHandler = container.get(symbols.grantBucketAccessCommandHandler); + commandHandler = container.get(symbols.revokeBucketAccessCommandHandler); sqliteDatabaseClient = container.get(coreSymbols.sqliteDatabaseClient); userTestUtils = container.get(testSymbols.userTestUtils); + userBucketTestUtils = container.get(testSymbols.userBucketTestUtils); + await userTestUtils.truncate(); + + await userBucketTestUtils.truncate(); }); afterEach(async () => { await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await sqliteDatabaseClient.destroy(); }); it('revokes bucket access', async () => { const user = await userTestUtils.createAndPersist(); - const bucketName = Generator.bucketName(); - - await userTestUtils.createAndPersistUserBucket({ - input: { - userId: user.id, - bucketName, - }, - }); + const userBucket = await userBucketTestUtils.createAndPersist({ input: { userId: user.id } }); await commandHandler.execute({ userId: user.id, - bucketName, + bucketName: userBucket.bucketName, }); - const userBuckets = await userTestUtils.findBucketsByUserId({ userId: user.id }); + const userBuckets = await userBucketTestUtils.findUserBuckets({ userId: user.id }); - expect(userBuckets).not.toContain(bucketName); + expect(userBuckets.length).toBe(0); }); it('throws an error when User does not exist', async () => { diff --git a/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.ts b/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.ts index 7e84896..382efc5 100644 --- a/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/commandHandlers/revokeBucketAccessCommandHandler/revokeBucketAccessCommandHandlerImpl.ts @@ -4,11 +4,13 @@ import { } from './revokeBucketAccessCommandHandler.js'; import { OperationNotValidError } from '../../../../../common/errors/common/operationNotValidError.js'; import { type LoggerService } from '../../../../../libs/logger/services/loggerService/loggerService.js'; +import { type UserBucketRepository } from '../../../domain/repositories/userBucketRepository/userBucketRepository.js'; import { type UserRepository } from '../../../domain/repositories/userRepository/userRepository.js'; export class RevokeBucketAccessCommandHandlerImpl implements RevokeBucketAccessCommandHandler { public constructor( private readonly userRepository: UserRepository, + private readonly userBucketRepository: UserBucketRepository, private readonly loggerService: LoggerService, ) {} @@ -30,7 +32,7 @@ export class RevokeBucketAccessCommandHandlerImpl implements RevokeBucketAccessC }); } - const existingBuckets = await this.userRepository.findUserBuckets({ userId }); + const existingBuckets = await this.userBucketRepository.findUserBuckets({ userId }); if (!existingBuckets.find((userBucket) => userBucket.getBucketName() === bucketName)) { this.loggerService.debug({ @@ -42,14 +44,7 @@ export class RevokeBucketAccessCommandHandlerImpl implements RevokeBucketAccessC return; } - existingUser.addRevokeBucketAccessAction({ - bucketName, - }); - - await this.userRepository.updateUser({ - id: userId, - domainActions: existingUser.getDomainActions(), - }); + await this.userBucketRepository.deleteUserBucket({ bucketName }); this.loggerService.debug({ message: 'Bucket access revoked.', diff --git a/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.integration.test.ts b/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.integration.test.ts index cb4cea4..136b95c 100644 --- a/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.integration.test.ts @@ -1,13 +1,13 @@ import { beforeEach, afterEach, expect, it, describe } from 'vitest'; -import { Generator } from '@common/tests'; - import { type FindUserBucketsQueryHandler } from './findUserBucketsQueryHandler.js'; -import { Application } from '../../../../../core/application.js'; +import { testSymbols } from '../../../../../../tests/container/symbols.js'; +import { TestContainer } from '../../../../../../tests/container/testContainer.js'; import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; import { coreSymbols } from '../../../../../core/symbols.js'; import { symbols } from '../../../symbols.js'; -import { UserTestUtils } from '../../../tests/utils/userTestUtils/userTestUtils.js'; +import { type UserBucketTestUtils } from '../../../tests/utils/userBucketTestUtils/userBucketTestUtils.js'; +import { type UserTestUtils } from '../../../tests/utils/userTestUtils/userTestUtils.js'; describe('FindUserBucketsQueryHandler', () => { let findUserBucketsQueryHandler: FindUserBucketsQueryHandler; @@ -16,38 +16,39 @@ describe('FindUserBucketsQueryHandler', () => { let userTestUtils: UserTestUtils; + let userBucketTestUtils: UserBucketTestUtils; + beforeEach(async () => { - const container = Application.createContainer(); + const container = TestContainer.create(); findUserBucketsQueryHandler = container.get(symbols.findUserBucketsQueryHandler); sqliteDatabaseClient = container.get(coreSymbols.sqliteDatabaseClient); - userTestUtils = new UserTestUtils(sqliteDatabaseClient); + userTestUtils = container.get(testSymbols.userTestUtils); + + userBucketTestUtils = container.get(testSymbols.userBucketTestUtils); await userTestUtils.truncate(); + + await userBucketTestUtils.truncate(); }); afterEach(async () => { await userTestUtils.truncate(); + await userBucketTestUtils.truncate(); + await sqliteDatabaseClient.destroy(); }); - it('finds User buckets', async () => { + it('finds UserBuckets', async () => { const user = await userTestUtils.createAndPersist(); - const bucketName = Generator.word(); - - await userTestUtils.createAndPersistUserBucket({ - input: { - userId: user.id, - bucketName, - }, - }); + const userBucket = await userBucketTestUtils.createAndPersist({ input: { userId: user.id } }); const { buckets } = await findUserBucketsQueryHandler.execute({ userId: user.id }); - expect(buckets).toEqual([bucketName]); + expect(buckets).toEqual([userBucket.bucketName]); }); }); diff --git a/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.ts b/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.ts index 6abf029..6f452aa 100644 --- a/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.ts +++ b/apps/backend/src/modules/userModule/application/queryHandlers/findUserBucketsQueryHandler/findUserBucketsQueryHandlerImpl.ts @@ -3,15 +3,15 @@ import { type FindUserBucketsQueryHandlerPayload, type FindUserBucketsQueryHandlerResult, } from './findUserBucketsQueryHandler.js'; -import { type UserRepository } from '../../../domain/repositories/userRepository/userRepository.js'; +import { type UserBucketRepository } from '../../../domain/repositories/userBucketRepository/userBucketRepository.js'; export class FindUserBucketsQueryHandlerImpl implements FindUserBucketsQueryHandler { - public constructor(private readonly userRepository: UserRepository) {} + public constructor(private readonly userBucketRepository: UserBucketRepository) {} public async execute(payload: FindUserBucketsQueryHandlerPayload): Promise { const { userId } = payload; - const userBuckets = await this.userRepository.findUserBuckets({ userId }); + const userBuckets = await this.userBucketRepository.findUserBuckets({ userId }); const buckets = userBuckets.map((bucket) => bucket.getBucketName()); diff --git a/apps/backend/src/modules/userModule/domain/entities/refreshToken/refreshToken.ts b/apps/backend/src/modules/userModule/domain/entities/refreshToken/refreshToken.ts deleted file mode 100644 index 9db56ba..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/refreshToken/refreshToken.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface RefreshTokenDraft { - readonly id: string; - readonly userId: string; - readonly token: string; - readonly expiresAt: Date; -} - -export class RefreshToken { - private readonly id: string; - private readonly userId: string; - private readonly token: string; - private readonly expiresAt: Date; - - public constructor(draft: RefreshTokenDraft) { - const { id, userId, token, expiresAt } = draft; - - this.id = id; - - this.userId = userId; - - this.token = token; - - this.expiresAt = expiresAt; - } - - public getId(): string { - return this.id; - } - - public getUserId(): string { - return this.userId; - } - - public getToken(): string { - return this.token; - } - - public getExpiresAt(): Date { - return this.expiresAt; - } -} diff --git a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/createRefreshTokenDomainAction.ts b/apps/backend/src/modules/userModule/domain/entities/user/domainActions/createRefreshTokenDomainAction.ts deleted file mode 100644 index 7b8d5c1..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/createRefreshTokenDomainAction.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type UserDomainActionType } from './userDomainActionType.js'; - -export interface CreateRefreshTokenDomainAction { - actionName: UserDomainActionType.createRefreshToken; - payload: { - token: string; - expiresAt: Date; - }; -} diff --git a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/grantBucketAccessDomainAction.ts b/apps/backend/src/modules/userModule/domain/entities/user/domainActions/grantBucketAccessDomainAction.ts deleted file mode 100644 index 0af51df..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/grantBucketAccessDomainAction.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type UserDomainActionType } from './userDomainActionType.js'; - -export interface GrantBucketAccessDomainAction { - actionName: UserDomainActionType.grantBucketAccess; - payload: { - bucketName: string; - }; -} diff --git a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/revokeBucketAccessDomainAction.ts b/apps/backend/src/modules/userModule/domain/entities/user/domainActions/revokeBucketAccessDomainAction.ts deleted file mode 100644 index 7cec1d0..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/revokeBucketAccessDomainAction.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type UserDomainActionType } from './userDomainActionType.js'; - -export interface RevokeBucketAccessDomainAction { - actionName: UserDomainActionType.revokeBucketAccess; - payload: { - bucketName: string; - }; -} diff --git a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainAction.ts b/apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainAction.ts deleted file mode 100644 index a30cbdc..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainAction.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type CreateRefreshTokenDomainAction } from './createRefreshTokenDomainAction.js'; -import { type GrantBucketAccessDomainAction } from './grantBucketAccessDomainAction.js'; -import { type RevokeBucketAccessDomainAction } from './revokeBucketAccessDomainAction.js'; - -export type UserDomainAction = - | CreateRefreshTokenDomainAction - | GrantBucketAccessDomainAction - | RevokeBucketAccessDomainAction; diff --git a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainActionType.ts b/apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainActionType.ts deleted file mode 100644 index 1bb1f4b..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/user/domainActions/userDomainActionType.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum UserDomainActionType { - createRefreshToken = 'createRefreshToken', - grantBucketAccess = 'grantBucketAccess', - revokeBucketAccess = 'revokeBucketAccess', -} diff --git a/apps/backend/src/modules/userModule/domain/entities/user/user.ts b/apps/backend/src/modules/userModule/domain/entities/user/user.ts index fde9a18..0262834 100644 --- a/apps/backend/src/modules/userModule/domain/entities/user/user.ts +++ b/apps/backend/src/modules/userModule/domain/entities/user/user.ts @@ -1,8 +1,5 @@ import { type UserRole } from '@common/contracts'; -import { type UserDomainAction } from './domainActions/userDomainAction.js'; -import { UserDomainActionType } from './domainActions/userDomainActionType.js'; - export interface UserDraft { readonly id: string; readonly email: string; @@ -10,9 +7,10 @@ export interface UserDraft { readonly role: UserRole; } -export interface CreateRefreshTokenPayload { - readonly token: string; - readonly expiresAt: Date; +export interface UserState { + email: string; + password: string; + role: UserRole; } export interface GrantBucketAccessPayload { @@ -25,22 +23,16 @@ export interface RevokeBucketAccessPayload { export class User { private id: string; - private email: string; - private password: string; - private role: UserRole; - - private domainActions: UserDomainAction[] = []; + private state: UserState; public constructor(draft: UserDraft) { - const { id, email, password } = draft; - - this.id = id; - - this.password = password; - - this.email = email; + this.id = draft.id; - this.role = draft.role; + this.state = { + email: draft.email, + password: draft.password, + role: draft.role, + }; } public getId(): string { @@ -48,61 +40,18 @@ export class User { } public getEmail(): string { - return this.email; + return this.state.email; } public getPassword(): string { - return this.password; + return this.state.password; } public getRole(): UserRole { - return this.role; - } - - public getState(): UserDraft { - return { - id: this.id, - email: this.email, - password: this.password, - role: this.role, - }; + return this.state.role; } - public getDomainActions(): UserDomainAction[] { - return this.domainActions; - } - - public addCreateRefreshTokenAction(payload: CreateRefreshTokenPayload): void { - const { token, expiresAt } = payload; - - this.domainActions.push({ - actionName: UserDomainActionType.createRefreshToken, - payload: { - token, - expiresAt, - }, - }); - } - - public addGrantBucketAccessAction(payload: GrantBucketAccessPayload): void { - const { bucketName } = payload; - - this.domainActions.push({ - actionName: UserDomainActionType.grantBucketAccess, - payload: { - bucketName, - }, - }); - } - - public addRevokeBucketAccessAction(payload: RevokeBucketAccessPayload): void { - const { bucketName } = payload; - - this.domainActions.push({ - actionName: UserDomainActionType.revokeBucketAccess, - payload: { - bucketName, - }, - }); + public getState(): UserState { + return this.state; } } diff --git a/apps/backend/src/modules/userModule/domain/entities/userTokens/userTokens.ts b/apps/backend/src/modules/userModule/domain/entities/userTokens/userTokens.ts deleted file mode 100644 index f379982..0000000 --- a/apps/backend/src/modules/userModule/domain/entities/userTokens/userTokens.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface UserTokens { - readonly refreshTokens: string[]; -} diff --git a/apps/backend/src/modules/userModule/domain/repositories/userBucketRepository/userBucketRepository.ts b/apps/backend/src/modules/userModule/domain/repositories/userBucketRepository/userBucketRepository.ts new file mode 100644 index 0000000..ad845ff --- /dev/null +++ b/apps/backend/src/modules/userModule/domain/repositories/userBucketRepository/userBucketRepository.ts @@ -0,0 +1,20 @@ +import { type UserBucket } from '../../entities/userBucket/userBucket.js'; + +export interface CreateUserBucketPayload { + readonly userId: string; + readonly bucketName: string; +} + +export interface FindUserBucketsPayload { + readonly userId: string; +} + +export interface DeleteUserBucketPayload { + readonly bucketName: string; +} + +export interface UserBucketRepository { + createUserBucket(input: CreateUserBucketPayload): Promise; + findUserBuckets(input: FindUserBucketsPayload): Promise; + deleteUserBucket(input: DeleteUserBucketPayload): Promise; +} diff --git a/apps/backend/src/modules/userModule/domain/repositories/userRepository/userRepository.ts b/apps/backend/src/modules/userModule/domain/repositories/userRepository/userRepository.ts index 7978e1e..3af338d 100644 --- a/apps/backend/src/modules/userModule/domain/repositories/userRepository/userRepository.ts +++ b/apps/backend/src/modules/userModule/domain/repositories/userRepository/userRepository.ts @@ -1,12 +1,7 @@ -import { type User } from '../../../domain/entities/user/user.js'; -import { type UserDomainAction } from '../../entities/user/domainActions/userDomainAction.js'; -import { type UserBucket } from '../../entities/userBucket/userBucket.js'; -import { type UserTokens } from '../../entities/userTokens/userTokens.js'; +import { type UserState, type User } from '../../../domain/entities/user/user.js'; -export interface CreateUserPayload { - readonly email: string; - readonly password: string; - readonly role: string; +export interface SaveUserPayload { + readonly user: UserState; } export interface FindUserPayload { @@ -19,34 +14,14 @@ export interface FindUsersPayload { readonly pageSize: number; } -export interface FindUsersResult { - readonly users: User[]; -} - -export interface FindUserTokensPayload { - readonly userId: string; -} - -export interface FindUserBucketsPayload { - readonly userId: string; -} - -export interface UpdateUserPayload { - readonly id: string; - readonly domainActions: UserDomainAction[]; -} - export interface DeleteUserPayload { readonly id: string; } export interface UserRepository { - createUser(input: CreateUserPayload): Promise; + saveUser(input: SaveUserPayload): Promise; findUser(input: FindUserPayload): Promise; findUsers(input: FindUsersPayload): Promise; countUsers(): Promise; - findUserTokens(input: FindUserTokensPayload): Promise; - findUserBuckets(input: FindUserBucketsPayload): Promise; - updateUser(input: UpdateUserPayload): Promise; deleteUser(input: DeleteUserPayload): Promise; } diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateRefreshTokenTableMigration.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateRefreshTokenTableMigration.ts deleted file mode 100644 index 1dfd82c..0000000 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateRefreshTokenTableMigration.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { type DatabaseClient } from '../../../../../../libs/database/clients/databaseClient/databaseClient.js'; -import { type Migration } from '../../../../../../libs/database/types/migration.js'; - -export class M3CreateRefreshTokenTableMigration implements Migration { - public name = 'M3CreateRefreshTokenTableMigration'; - - private readonly tableName = 'refreshTokens'; - - private readonly columns = { - id: 'id', - userId: 'userId', - token: 'token', - expiresAt: 'expiresAt', - } as const; - - public async up(databaseClient: DatabaseClient): Promise { - await databaseClient.schema.createTable(this.tableName, (table) => { - table.text(this.columns.id).notNullable(); - - table.text(this.columns.userId).notNullable(); - - table.text(this.columns.token).notNullable(); - - table.timestamp(this.columns.expiresAt).notNullable(); - - table.primary([this.columns.id]); - - table.unique([this.columns.token]); - - table.foreign(this.columns.userId).references('id').inTable('users').onDelete('CASCADE'); - }); - } - - public async down(databaseClient: DatabaseClient): Promise { - await databaseClient.schema.dropTable(this.tableName); - } -} diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m4CreateUserBucketTableMigration.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateUserBucketTableMigration.ts similarity index 85% rename from apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m4CreateUserBucketTableMigration.ts rename to apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateUserBucketTableMigration.ts index 8dcefdc..a9d986c 100644 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m4CreateUserBucketTableMigration.ts +++ b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/migrations/m3CreateUserBucketTableMigration.ts @@ -1,8 +1,8 @@ import { type DatabaseClient } from '../../../../../../libs/database/clients/databaseClient/databaseClient.js'; import { type Migration } from '../../../../../../libs/database/types/migration.js'; -export class M4CreateUserBucketTableMigration implements Migration { - public readonly name = 'M4CreateUserBucketTableMigration'; +export class M3CreateUserBucketTableMigration implements Migration { + public readonly name = 'M3CreateUserBucketTableMigration'; public async up(databaseClient: DatabaseClient): Promise { await databaseClient.schema.createTable('userBuckets', (table) => { diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/blacklistTokenTable/blacklistTokenTable.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/blacklistTokenTable/blacklistTokenTable.ts index d347e3e..37ddecf 100644 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/blacklistTokenTable/blacklistTokenTable.ts +++ b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/blacklistTokenTable/blacklistTokenTable.ts @@ -1,11 +1,5 @@ -import { type BlacklistTokenRawEntity } from './blacklistTokenRawEntity.js'; import { type DatabaseTable } from '../../../../../../../common/types/databaseTable.js'; -export class BlacklistTokenTable implements DatabaseTable { +export class BlacklistTokenTable implements DatabaseTable { public readonly name = 'blacklistTokens'; - public readonly columns = { - id: 'id', - token: 'token', - expiresAt: 'expiresAt', - } as const; } diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenRawEntity.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenRawEntity.ts deleted file mode 100644 index f7d73bd..0000000 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenRawEntity.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface RefreshTokenRawEntity { - readonly id: string; - readonly userId: string; - readonly token: string; - readonly expiresAt: Date; -} diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenTable.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenTable.ts deleted file mode 100644 index ba1b977..0000000 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenTable.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type RefreshTokenRawEntity } from './refreshTokenRawEntity.js'; -import { type DatabaseTable } from '../../../../../../../common/types/databaseTable.js'; - -export class RefreshTokenTable implements DatabaseTable { - public readonly name = 'refreshTokens'; - public readonly columns = { - id: 'id', - userId: 'userId', - token: 'token', - expiresAt: 'expiresAt', - } as const; -} diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userBucketTable/userBucketTable.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userBucketTable/userBucketTable.ts index 962eacc..404e958 100644 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userBucketTable/userBucketTable.ts +++ b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userBucketTable/userBucketTable.ts @@ -1,11 +1,5 @@ -import { type UserBucketRawEntity } from './userBucketRawEntity.js'; import { type DatabaseTable } from '../../../../../../../common/types/databaseTable.js'; -export class UserBucketTable implements DatabaseTable { +export class UserBucketTable implements DatabaseTable { public readonly name = 'userBuckets'; - public readonly columns = { - id: 'id', - userId: 'userId', - bucketName: 'bucketName', - } as const; } diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userRawEntity.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userRawEntity.ts index 95021c1..387f2d8 100644 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userRawEntity.ts +++ b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userRawEntity.ts @@ -1,6 +1,8 @@ +import { type UserRole } from '@common/contracts'; + export interface UserRawEntity { readonly id: string; readonly email: string; readonly password: string; - readonly role: string; + readonly role: UserRole; } diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userTable.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userTable.ts index 203d19e..36322f8 100644 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userTable.ts +++ b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/tables/userTable/userTable.ts @@ -1,12 +1,5 @@ -import { type UserRawEntity } from './userRawEntity.js'; import { type DatabaseTable } from '../../../../../../../common/types/databaseTable.js'; -export class UserTable implements DatabaseTable { +export class UserTable implements DatabaseTable { public readonly name = 'users'; - public readonly columns = { - id: 'id', - email: 'email', - password: 'password', - role: 'role', - } as const; } diff --git a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/userDatabaseMigrationSource.ts b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/userDatabaseMigrationSource.ts index fe1d67b..2eea69e 100644 --- a/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/userDatabaseMigrationSource.ts +++ b/apps/backend/src/modules/userModule/infrastructure/databases/userDatabase/userDatabaseMigrationSource.ts @@ -1,7 +1,6 @@ import { M1CreateUserTableMigration } from './migrations/m1CreateUserTableMigration.js'; import { M2CreateBlacklistTokenTableMigration } from './migrations/m2CreateBlacklistTokenTableMigration.js'; -import { M3CreateRefreshTokenTableMigration } from './migrations/m3CreateRefreshTokenTableMigration.js'; -import { M4CreateUserBucketTableMigration } from './migrations/m4CreateUserBucketTableMigration.js'; +import { M3CreateUserBucketTableMigration } from './migrations/m3CreateUserBucketTableMigration.js'; import { type Migration } from '../../../../../libs/database/types/migration.js'; import { type MigrationSource } from '../../../../../libs/database/types/migrationSource.js'; @@ -10,8 +9,7 @@ export class UserDatabaseMigrationSource implements MigrationSource { return [ new M1CreateUserTableMigration(), new M2CreateBlacklistTokenTableMigration(), - new M3CreateRefreshTokenTableMigration(), - new M4CreateUserBucketTableMigration(), + new M3CreateUserBucketTableMigration(), ]; } diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/blacklistTokenRepository/blacklistTokenRepositoryImpl.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/blacklistTokenRepository/blacklistTokenRepositoryImpl.ts index c22ca3e..e0f6199 100644 --- a/apps/backend/src/modules/userModule/infrastructure/repositories/blacklistTokenRepository/blacklistTokenRepositoryImpl.ts +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/blacklistTokenRepository/blacklistTokenRepositoryImpl.ts @@ -1,7 +1,6 @@ import { type BlacklistTokenMapper } from './blacklistTokenMapper/blacklistTokenMapper.js'; import { RepositoryError } from '../../../../../common/errors/common/repositoryError.js'; import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; -import { type QueryBuilder } from '../../../../../libs/database/types/queryBuilder.js'; import { type LoggerService } from '../../../../../libs/logger/services/loggerService/loggerService.js'; import { type UuidService } from '../../../../../libs/uuid/services/uuidService/uuidService.js'; import { type BlacklistToken } from '../../../domain/entities/blacklistToken/blacklistToken.js'; @@ -23,21 +22,17 @@ export class BlacklistTokenRepositoryImpl implements BlacklistTokenRepository { private readonly loggerService: LoggerService, ) {} - private createBlacklistTokenQueryBuilder(): QueryBuilder { - return this.sqliteDatabaseClient(this.blacklistTokenDatabaseTable.name); - } - public async createBlacklistToken(payload: CreateBlacklistTokenPayload): Promise { const { token, expiresAt } = payload; - const queryBuilder = this.createBlacklistTokenQueryBuilder(); - let rawEntities: BlacklistTokenRawEntity[]; const id = this.uuidService.generateUuid(); try { - rawEntities = await queryBuilder.insert( + rawEntities = await this.sqliteDatabaseClient( + this.blacklistTokenDatabaseTable.name, + ).insert( { id, token, @@ -67,12 +62,13 @@ export class BlacklistTokenRepositoryImpl implements BlacklistTokenRepository { public async findBlacklistToken(payload: FindBlacklistTokenPayload): Promise { const { token } = payload; - const queryBuilder = this.createBlacklistTokenQueryBuilder(); - let rawEntity: BlacklistTokenRawEntity | undefined; try { - rawEntity = await queryBuilder.select('*').where({ token }).first(); + rawEntity = await this.sqliteDatabaseClient(this.blacklistTokenDatabaseTable.name) + .select('*') + .where({ token }) + .first(); } catch (error) { if (error instanceof Error) { this.loggerService.error({ diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapper.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapper.ts similarity index 100% rename from apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapper.ts rename to apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapper.ts diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapperImpl.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapperImpl.ts similarity index 100% rename from apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapperImpl.ts rename to apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapperImpl.ts diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapperImpl.unit.test.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapperImpl.unit.test.ts similarity index 65% rename from apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapperImpl.unit.test.ts rename to apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapperImpl.unit.test.ts index 8b7545e..f6141d8 100644 --- a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userBucketMapper/userBucketMapperImpl.unit.test.ts +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapperImpl.unit.test.ts @@ -1,15 +1,15 @@ import { expect, describe, it } from 'vitest'; import { UserBucketMapperImpl } from './userBucketMapperImpl.js'; -import { UserBucketEntityTestFactory } from '../../../../tests/factories/userBucketEntityTestFactory/userBucketEntityTestFactory.js'; +import { UserBucketTestFactory } from '../../../../tests/factories/userBucketTestFactory/userBucketTestFactory.js'; describe('UserBucketMapperImpl', () => { const userBucketMapperImpl = new UserBucketMapperImpl(); - const userBucketEntityTestFactory = new UserBucketEntityTestFactory(); + const userBucketTestFactory = new UserBucketTestFactory(); it('maps from UserBucketRawEntity to UserBucket', async () => { - const userBucketEntity = userBucketEntityTestFactory.create(); + const userBucketEntity = userBucketTestFactory.createRaw(); const userBucket = userBucketMapperImpl.mapToDomain(userBucketEntity); diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.integration.test.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.integration.test.ts new file mode 100644 index 0000000..ca45999 --- /dev/null +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.integration.test.ts @@ -0,0 +1,112 @@ +import { beforeEach, afterEach, expect, describe, it } from 'vitest'; + +import { Generator } from '@common/tests'; + +import { RepositoryError } from '../../../../../common/errors/common/repositoryError.js'; +import { Application } from '../../../../../core/application.js'; +import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; +import { coreSymbols } from '../../../../../core/symbols.js'; +import { type UserBucketRepository } from '../../../domain/repositories/userBucketRepository/userBucketRepository.js'; +import { symbols } from '../../../symbols.js'; +import { UserBucketTestUtils } from '../../../tests/utils/userBucketTestUtils/userBucketTestUtils.js'; +import { UserTestUtils } from '../../../tests/utils/userTestUtils/userTestUtils.js'; + +describe('UserBucketRepositoryImpl', () => { + let userBucketRepository: UserBucketRepository; + + let sqliteDatabaseClient: SqliteDatabaseClient; + + let userTestUtils: UserTestUtils; + + let userBucketTestUtils: UserBucketTestUtils; + + beforeEach(async () => { + const container = Application.createContainer(); + + userBucketRepository = container.get(symbols.userBucketRepository); + + sqliteDatabaseClient = container.get(coreSymbols.sqliteDatabaseClient); + + userTestUtils = new UserTestUtils(sqliteDatabaseClient); + + userBucketTestUtils = new UserBucketTestUtils(sqliteDatabaseClient); + + await userTestUtils.truncate(); + + await userBucketTestUtils.truncate(); + }); + + afterEach(async () => { + await userTestUtils.truncate(); + + await userBucketTestUtils.truncate(); + + await sqliteDatabaseClient.destroy(); + }); + + describe('Create', () => { + it('creates a UserBucket', async () => { + const user = await userTestUtils.createAndPersist(); + + const bucketName = Generator.word(); + + const userBucket = await userBucketRepository.createUserBucket({ + userId: user.id, + bucketName, + }); + + const foundUserBucket = await userBucketTestUtils.findUserBucket({ bucketName }); + + expect(userBucket.getBucketName()).toEqual(bucketName); + + expect(foundUserBucket.bucketName).toEqual(bucketName); + }); + + it('throws an error when a UserBucket with the same bucket name already exists', async () => { + const user = await userTestUtils.createAndPersist(); + + const existingUserBucket = await userBucketTestUtils.createAndPersist({ input: { userId: user.id } }); + + try { + await userBucketRepository.createUserBucket({ + userId: user.id, + bucketName: existingUserBucket.bucketName, + }); + } catch (error) { + expect(error).toBeInstanceOf(RepositoryError); + + return; + } + + expect.fail(); + }); + }); + + describe('Delete', () => { + it('deletes a UserBucket', async () => { + const user = await userTestUtils.createAndPersist(); + + const existingUserBucket = await userBucketTestUtils.createAndPersist({ input: { userId: user.id } }); + + await userBucketRepository.deleteUserBucket({ bucketName: existingUserBucket.bucketName }); + + const foundUserBucket = await userBucketTestUtils.findUserBucket({ bucketName: existingUserBucket.bucketName }); + + expect(foundUserBucket).toBeUndefined(); + }); + }); + + describe('Find', () => { + it('finds UserBuckets', async () => { + const user = await userTestUtils.createAndPersist(); + + const existingUserBucket = await userBucketTestUtils.createAndPersist({ input: { userId: user.id } }); + + const userBuckets = await userBucketRepository.findUserBuckets({ userId: user.id }); + + expect(userBuckets.length).toEqual(1); + + expect(userBuckets[0]?.getBucketName()).toEqual(existingUserBucket.bucketName); + }); + }); +}); diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.ts new file mode 100644 index 0000000..e1aad32 --- /dev/null +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.ts @@ -0,0 +1,82 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { type UserBucketMapper } from './userBucketMapper/userBucketMapper.js'; +import { RepositoryError } from '../../../../../common/errors/common/repositoryError.js'; +import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; +import { type UuidService } from '../../../../../libs/uuid/services/uuidService/uuidService.js'; +import { type UserBucket } from '../../../domain/entities/userBucket/userBucket.js'; +import { + type FindUserBucketsPayload, + type CreateUserBucketPayload, + type UserBucketRepository, + type DeleteUserBucketPayload, +} from '../../../domain/repositories/userBucketRepository/userBucketRepository.js'; +import { type UserBucketRawEntity } from '../../databases/userDatabase/tables/userBucketTable/userBucketRawEntity.js'; +import { UserBucketTable } from '../../databases/userDatabase/tables/userBucketTable/userBucketTable.js'; + +export class UserBucketRepositoryImpl implements UserBucketRepository { + private readonly userBucketTable = new UserBucketTable(); + + public constructor( + private readonly sqliteDatabaseClient: SqliteDatabaseClient, + private readonly userBucketMapper: UserBucketMapper, + private readonly uuidService: UuidService, + ) {} + + public async createUserBucket(payload: CreateUserBucketPayload): Promise { + const { bucketName, userId } = payload; + + let rawEntities: UserBucketRawEntity[] = []; + + try { + rawEntities = await this.sqliteDatabaseClient(this.userBucketTable.name).insert( + { + id: this.uuidService.generateUuid(), + bucketName, + userId, + }, + '*', + ); + } catch (error) { + throw new RepositoryError({ + entity: 'UserBucket', + operation: 'create', + }); + } + + const rawEntity = rawEntities[0] as UserBucketRawEntity; + + return this.userBucketMapper.mapToDomain(rawEntity); + } + + public async findUserBuckets(payload: FindUserBucketsPayload): Promise { + const { userId } = payload; + + let rawEntities: UserBucketRawEntity[]; + + try { + rawEntities = await this.sqliteDatabaseClient(this.userBucketTable.name) + .select('*') + .where({ userId }); + } catch (error) { + throw new RepositoryError({ + entity: 'UserBuckets', + operation: 'find', + }); + } + + return rawEntities.map((rawEntity) => this.userBucketMapper.mapToDomain(rawEntity)); + } + + public async deleteUserBucket(payload: DeleteUserBucketPayload): Promise { + const { bucketName } = payload; + + try { + await this.sqliteDatabaseClient(this.userBucketTable.name).delete().where({ bucketName }); + } catch (error) { + throw new RepositoryError({ + entity: 'UserBucket', + operation: 'delete', + }); + } + } +} diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.ts index d6d294b..c07dc46 100644 --- a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.ts +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.ts @@ -1,5 +1,3 @@ -import { type UserRole } from '@common/contracts'; - import { type UserMapper } from './userMapper.js'; import { User } from '../../../../domain/entities/user/user.js'; import { type UserRawEntity } from '../../../databases/userDatabase/tables/userTable/userRawEntity.js'; @@ -12,7 +10,7 @@ export class UserMapperImpl implements UserMapper { id, email, password, - role: role as UserRole, + role, }); } } diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.unit.test.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.unit.test.ts index 2c37e3d..0530050 100644 --- a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.unit.test.ts +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userMapper/userMapperImpl.unit.test.ts @@ -1,24 +1,24 @@ import { expect, describe, it } from 'vitest'; import { UserMapperImpl } from './userMapperImpl.js'; -import { UserEntityTestFactory } from '../../../../tests/factories/userEntityTestFactory/userEntityTestFactory.js'; +import { UserTestFactory } from '../../../../tests/factories/userTestFactory/userTestFactory.js'; describe('UserMapperImpl', () => { const userMapperImpl = new UserMapperImpl(); - const userEntityTestFactory = new UserEntityTestFactory(); + const userTestFactory = new UserTestFactory(); it('maps from UserRawEntity to User', async () => { - const userEntity = userEntityTestFactory.create(); + const userEntity = userTestFactory.createRaw(); const user = userMapperImpl.mapToDomain(userEntity); - expect(user).toEqual({ - id: userEntity.id, + expect(user.getId()).toEqual(userEntity.id); + + expect(user.getState()).toEqual({ email: userEntity.email, password: userEntity.password, role: userEntity.role, - domainActions: [], }); }); }); diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.integration.test.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.integration.test.ts index f77f40e..ef6c71a 100644 --- a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.integration.test.ts +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.integration.test.ts @@ -1,7 +1,5 @@ import { beforeEach, afterEach, expect, describe, it } from 'vitest'; -import { Generator } from '@common/tests'; - import { RepositoryError } from '../../../../../common/errors/common/repositoryError.js'; import { ResourceNotFoundError } from '../../../../../common/errors/common/resourceNotFoundError.js'; import { Application } from '../../../../../core/application.js'; @@ -45,10 +43,12 @@ describe('UserRepositoryImpl', () => { const { email, password, role } = createdUser.getState(); - const user = await userRepository.createUser({ - email, - password, - role, + const user = await userRepository.saveUser({ + user: { + email, + password, + role, + }, }); const foundUser = await userTestUtils.findByEmail({ email }); @@ -62,10 +62,12 @@ describe('UserRepositoryImpl', () => { const existingUser = await userTestUtils.createAndPersist(); try { - await userRepository.createUser({ - email: existingUser.email, - password: existingUser.password, - role: existingUser.role, + await userRepository.saveUser({ + user: { + email: existingUser.email, + password: existingUser.password, + role: existingUser.role, + }, }); } catch (error) { expect(error).toBeInstanceOf(RepositoryError); @@ -160,100 +162,6 @@ describe('UserRepositoryImpl', () => { }); }); - describe('Update', () => { - it(`creates User's refresh tokens`, async () => { - const user = await userTestUtils.createAndPersist(); - - const createdUser = userTestFactory.create(); - - const refreshToken1 = Generator.alphaString(32); - - const expiresAt1 = Generator.futureDate(); - - const refreshToken2 = Generator.alphaString(32); - - const expiresAt2 = Generator.futureDate(); - - createdUser.addCreateRefreshTokenAction({ - token: refreshToken1, - expiresAt: expiresAt1, - }); - - createdUser.addCreateRefreshTokenAction({ - token: refreshToken2, - expiresAt: expiresAt2, - }); - - await userRepository.updateUser({ - id: user.id, - domainActions: createdUser.getDomainActions(), - }); - - const updatedUserTokens = await userTestUtils.findTokensByUserId({ userId: user.id }); - - expect(updatedUserTokens.refreshTokens.includes(refreshToken1)).toBe(true); - - expect(updatedUserTokens.refreshTokens.includes(refreshToken2)).toBe(true); - }); - - it(`add User's bucket`, async () => { - const user = await userTestUtils.createAndPersist(); - - const createdUser = userTestFactory.create(); - - const bucketName = Generator.word(); - - createdUser.addGrantBucketAccessAction({ bucketName }); - - await userRepository.updateUser({ - id: user.id, - domainActions: createdUser.getDomainActions(), - }); - - const buckets = await userTestUtils.findBucketsByUserId({ userId: user.id }); - - expect(buckets.length).toEqual(1); - - expect(buckets[0]?.bucketName).toEqual(bucketName); - }); - - it(`removes User's bucket`, async () => { - const user = await userTestUtils.createAndPersist(); - - const { bucketName } = await userTestUtils.createAndPersistUserBucket({ input: { userId: user.id } }); - - const createdUser = userTestFactory.create(); - - createdUser.addRevokeBucketAccessAction({ bucketName }); - - await userRepository.updateUser({ - id: user.id, - domainActions: createdUser.getDomainActions(), - }); - - const buckets = await userTestUtils.findBucketsByUserId({ userId: user.id }); - - expect(buckets.length).toEqual(0); - }); - - it('throws an error if a User with given id does not exist', async () => { - const nonExistentUser = userTestFactory.create(); - - try { - await userRepository.updateUser({ - id: nonExistentUser.getId(), - domainActions: nonExistentUser.getDomainActions(), - }); - } catch (error) { - expect(error).toBeInstanceOf(ResourceNotFoundError); - - return; - } - - expect.fail(); - }); - }); - describe('Delete', () => { it('deletes a User', async () => { const user = await userTestUtils.createAndPersist(); @@ -279,90 +187,4 @@ describe('UserRepositoryImpl', () => { expect.fail(); }); }); - - describe('Find tokens', () => { - it('finds User tokens by userId', async () => { - const user = await userTestUtils.createAndPersist(); - - const refreshToken1 = Generator.alphaString(32); - - const expiresAt1 = Generator.futureDate(); - - const refreshToken2 = Generator.alphaString(32); - - const expiresAt2 = Generator.futureDate(); - - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken1, - expiresAt: expiresAt1, - }, - }); - - await userTestUtils.createAndPersistRefreshToken({ - input: { - userId: user.id, - token: refreshToken2, - expiresAt: expiresAt2, - }, - }); - - const userTokens = await userRepository.findUserTokens({ userId: user.id }); - - expect(userTokens).not.toBeNull(); - - expect(userTokens!.refreshTokens.includes(refreshToken1)).toBe(true); - - expect(userTokens!.refreshTokens.includes(refreshToken2)).toBe(true); - }); - - it('returns null if a User with given id does not exist', async () => { - const nonExistentUser = userTestFactory.create(); - - const userTokens = await userRepository.findUserTokens({ userId: nonExistentUser.getId() }); - - expect(userTokens).toBeNull(); - }); - }); - - describe('Find buckets', () => { - it('finds User buckets', async () => { - const user = await userTestUtils.createAndPersist(); - - const bucketName1 = 'bucket1'; - - const bucketName2 = 'bucket2'; - - await userTestUtils.createAndPersistUserBucket({ - input: { - userId: user.id, - bucketName: bucketName1, - }, - }); - - await userTestUtils.createAndPersistUserBucket({ - input: { - userId: user.id, - bucketName: bucketName2, - }, - }); - - const buckets = await userRepository.findUserBuckets({ userId: user.id }); - - expect(buckets.length).toEqual(2); - - expect(buckets.find((bucket) => bucket.getBucketName() === bucketName1)).not.toBeNull(); - - expect(buckets.find((bucket) => bucket.getBucketName() === bucketName2)).not.toBeNull(); - }); - - it('returns empty array if a User with given id does not exist', async () => { - const nonExistentUser = userTestFactory.create(); - - const buckets = await userRepository.findUserBuckets({ userId: nonExistentUser.getId() }); - - expect(buckets.length).toEqual(0); - }); - }); }); diff --git a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.ts b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.ts index 355bbdf..0dd1056 100644 --- a/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.ts +++ b/apps/backend/src/modules/userModule/infrastructure/repositories/userRepository/userRepositoryImpl.ts @@ -1,87 +1,46 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { type UserBucketMapper } from './userBucketMapper/userBucketMapper.js'; import { type UserMapper } from './userMapper/userMapper.js'; import { OperationNotValidError } from '../../../../../common/errors/common/operationNotValidError.js'; import { RepositoryError } from '../../../../../common/errors/common/repositoryError.js'; import { ResourceNotFoundError } from '../../../../../common/errors/common/resourceNotFoundError.js'; import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; -import { type LoggerService } from '../../../../../libs/logger/services/loggerService/loggerService.js'; import { type UuidService } from '../../../../../libs/uuid/services/uuidService/uuidService.js'; -import { type UserDomainAction } from '../../../domain/entities/user/domainActions/userDomainAction.js'; -import { UserDomainActionType } from '../../../domain/entities/user/domainActions/userDomainActionType.js'; import { type User } from '../../../domain/entities/user/user.js'; -import { type UserBucket } from '../../../domain/entities/userBucket/userBucket.js'; -import { type UserTokens } from '../../../domain/entities/userTokens/userTokens.js'; import { type UserRepository, - type CreateUserPayload, + type SaveUserPayload, type FindUserPayload, - type UpdateUserPayload, type DeleteUserPayload, - type FindUserTokensPayload, - type FindUserBucketsPayload, type FindUsersPayload, } from '../../../domain/repositories/userRepository/userRepository.js'; -import { type RefreshTokenRawEntity } from '../../databases/userDatabase/tables/refreshTokenTable/refreshTokenRawEntity.js'; -import { RefreshTokenTable } from '../../databases/userDatabase/tables/refreshTokenTable/refreshTokenTable.js'; -import { type UserBucketRawEntity } from '../../databases/userDatabase/tables/userBucketTable/userBucketRawEntity.js'; -import { UserBucketTable } from '../../databases/userDatabase/tables/userBucketTable/userBucketTable.js'; import { type UserRawEntity } from '../../databases/userDatabase/tables/userTable/userRawEntity.js'; import { UserTable } from '../../databases/userDatabase/tables/userTable/userTable.js'; -interface TokenValue { - readonly token: string; - readonly expiresAt: Date; -} - -export interface MappedUserUpdate { - readonly refreshTokenCreatePayloads: TokenValue[]; - readonly bucketsToGrantAccess: string[]; - readonly bucketsToRevokeAccess: string[]; -} - -export interface FindRefreshTokensPayload { - readonly userId: string; -} - export class UserRepositoryImpl implements UserRepository { private readonly userTable = new UserTable(); - private readonly userBucketTable = new UserBucketTable(); - private readonly refreshTokenTable = new RefreshTokenTable(); public constructor( private readonly sqliteDatabaseClient: SqliteDatabaseClient, private readonly userMapper: UserMapper, - private readonly userBucketMapper: UserBucketMapper, private readonly uuidService: UuidService, - private readonly loggerService: LoggerService, ) {} - public async createUser(payload: CreateUserPayload): Promise { - const { email, password, role } = payload; + public async saveUser(payload: SaveUserPayload): Promise { + const { user } = payload; let rawEntities: UserRawEntity[] = []; - const id = this.uuidService.generateUuid(); - try { - await this.sqliteDatabaseClient.transaction(async (transaction) => { - rawEntities = await transaction(this.userTable.name).insert( - { - id, - email, - password, - role, - }, - '*', - ); - }); + rawEntities = await this.sqliteDatabaseClient(this.userTable.name).insert( + { + id: this.uuidService.generateUuid(), + email: user.email, + password: user.password, + role: user.role, + }, + '*', + ); } catch (error) { - this.loggerService.error({ - message: 'Error while creating User.', - error, - }); - throw new RepositoryError({ entity: 'User', operation: 'create', @@ -126,11 +85,6 @@ export class UserRepositoryImpl implements UserRepository { .where(whereCondition) .first(); } catch (error) { - this.loggerService.error({ - message: 'Error while finding User.', - error, - }); - throw new RepositoryError({ entity: 'User', operation: 'find', @@ -155,11 +109,6 @@ export class UserRepositoryImpl implements UserRepository { .offset((page - 1) * pageSize) .limit(pageSize); } catch (error) { - this.loggerService.error({ - message: 'Error while finding Users.', - error, - }); - throw new RepositoryError({ entity: 'Users', operation: 'find', @@ -182,11 +131,6 @@ export class UserRepositoryImpl implements UserRepository { return Number((result as any).count); } catch (error) { - this.loggerService.error({ - message: 'Error while counting Users.', - error, - }); - throw new RepositoryError({ entity: 'Users', operation: 'count', @@ -194,178 +138,6 @@ export class UserRepositoryImpl implements UserRepository { } } - public async findUserBuckets(payload: FindUserBucketsPayload): Promise { - const { userId } = payload; - - let rawEntities: UserBucketRawEntity[]; - - try { - rawEntities = await this.sqliteDatabaseClient(this.userBucketTable.name) - .select('*') - .where({ userId }); - } catch (error) { - this.loggerService.error({ - message: 'Error while finding UserBucket.', - error, - }); - - throw new RepositoryError({ - entity: 'UserBuckets', - operation: 'find', - }); - } - - return rawEntities.map((rawEntity) => this.userBucketMapper.mapToDomain(rawEntity)); - } - - public async findUserTokens(payload: FindUserTokensPayload): Promise { - const { userId } = payload; - - const refreshTokens = await this.findRefreshTokens({ userId }); - - if (!refreshTokens.length) { - return null; - } - - return { - refreshTokens: refreshTokens.map((refreshToken) => refreshToken.token), - }; - } - - private async findRefreshTokens(payload: FindRefreshTokensPayload): Promise { - const { userId } = payload; - - let rawEntities: RefreshTokenRawEntity[]; - - try { - rawEntities = await this.sqliteDatabaseClient(this.refreshTokenTable.name) - .select('*') - .where({ userId }); - } catch (error) { - this.loggerService.error({ - message: 'Error while finding RefreshToken.', - error, - }); - - throw new RepositoryError({ - entity: 'RefreshToken', - operation: 'find', - }); - } - - return rawEntities; - } - - public async updateUser(payload: UpdateUserPayload): Promise { - const { id, domainActions } = payload; - - const existingUser = await this.findUser({ id }); - - if (!existingUser) { - throw new ResourceNotFoundError({ - name: 'User', - id, - }); - } - - const { refreshTokenCreatePayloads, bucketsToGrantAccess, bucketsToRevokeAccess } = - this.mapDomainActionsToUpdatePayload(domainActions); - - try { - await this.sqliteDatabaseClient.transaction(async (transaction) => { - if (refreshTokenCreatePayloads.length) { - await transaction(this.refreshTokenTable.name).insert( - refreshTokenCreatePayloads.map((refreshTokenCreatePayload) => ({ - id: this.uuidService.generateUuid(), - userId: id, - ...refreshTokenCreatePayload, - })), - ); - } - - if (bucketsToGrantAccess.length > 0) { - const drafts = bucketsToGrantAccess.map((bucketName) => ({ - id: this.uuidService.generateUuid(), - userId: id, - bucketName, - })); - - await transaction(this.userBucketTable.name).insert(drafts); - } - - if (bucketsToRevokeAccess.length > 0) { - await transaction(this.userBucketTable.name) - .delete() - .whereIn(this.userBucketTable.columns.bucketName, bucketsToRevokeAccess); - } - }); - } catch (error) { - const errorContext = - error instanceof Error - ? { - errorMessage: error.message, - errorName: error.name, - } - : {}; - - this.loggerService.error({ - message: 'Error while updating User.', - error: errorContext, - }); - - throw new RepositoryError({ - entity: 'User', - operation: 'update', - }); - } - } - - private mapDomainActionsToUpdatePayload(domainActions: UserDomainAction[]): MappedUserUpdate { - const refreshTokenCreatePayloads: TokenValue[] = []; - - const bucketsToGrantAccess: string[] = []; - - const bucketsToRevokeAccess: string[] = []; - - domainActions.forEach((domainAction) => { - switch (domainAction.actionName) { - case UserDomainActionType.createRefreshToken: - refreshTokenCreatePayloads.push({ - token: domainAction.payload.token, - expiresAt: domainAction.payload.expiresAt, - }); - - break; - - case UserDomainActionType.grantBucketAccess: - bucketsToGrantAccess.push(domainAction.payload.bucketName); - - break; - - case UserDomainActionType.revokeBucketAccess: - bucketsToRevokeAccess.push(domainAction.payload.bucketName); - - break; - - default: - this.loggerService.error({ - message: 'Error mapping domain actions.', - }); - - throw new RepositoryError({ - entity: 'User', - operation: 'update', - }); - } - }); - - return { - refreshTokenCreatePayloads, - bucketsToGrantAccess, - bucketsToRevokeAccess, - }; - } - public async deleteUser(payload: DeleteUserPayload): Promise { const { id } = payload; @@ -381,11 +153,6 @@ export class UserRepositoryImpl implements UserRepository { try { await this.sqliteDatabaseClient(this.userTable.name).delete().where({ id }); } catch (error) { - this.loggerService.error({ - message: 'Error while deleting User.', - error, - }); - throw new RepositoryError({ entity: 'User', operation: 'delete', diff --git a/apps/backend/src/modules/userModule/symbols.ts b/apps/backend/src/modules/userModule/symbols.ts index 4206537..7adae57 100644 --- a/apps/backend/src/modules/userModule/symbols.ts +++ b/apps/backend/src/modules/userModule/symbols.ts @@ -3,6 +3,7 @@ export const symbols = { userMapper: Symbol('userMapper'), userBucketMapper: Symbol('userBucketMapper'), userRepository: Symbol('userRepository'), + userBucketRepository: Symbol('userBucketRepository'), blacklistTokenMapper: Symbol('blacklistTokenMapper'), blacklistTokenRepository: Symbol('blacklistTokenRepository'), diff --git a/apps/backend/src/modules/userModule/tests/factories/refreshTokenTestFactory/refreshTokenTestFactory.ts b/apps/backend/src/modules/userModule/tests/factories/refreshTokenTestFactory/refreshTokenTestFactory.ts deleted file mode 100644 index c8587ea..0000000 --- a/apps/backend/src/modules/userModule/tests/factories/refreshTokenTestFactory/refreshTokenTestFactory.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Generator } from '@common/tests'; - -import { type RefreshTokenDraft, RefreshToken } from '../../../domain/entities/refreshToken/refreshToken.js'; - -export class RefreshTokenTestFactory { - public create(input: Partial = {}): RefreshToken { - return new RefreshToken({ - id: Generator.uuid(), - userId: Generator.uuid(), - token: Generator.alphaString(32), - expiresAt: Generator.futureDate(), - ...input, - }); - } -} diff --git a/apps/backend/src/modules/userModule/tests/factories/userBucketEntityTestFactory/userBucketEntityTestFactory.ts b/apps/backend/src/modules/userModule/tests/factories/userBucketEntityTestFactory/userBucketEntityTestFactory.ts deleted file mode 100644 index 6a1bec8..0000000 --- a/apps/backend/src/modules/userModule/tests/factories/userBucketEntityTestFactory/userBucketEntityTestFactory.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Generator } from '@common/tests'; - -import { type UserBucketRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userBucketTable/userBucketRawEntity.js'; - -export class UserBucketEntityTestFactory { - public create(input: Partial = {}): UserBucketRawEntity { - return { - id: Generator.uuid(), - userId: Generator.uuid(), - bucketName: Generator.word(), - ...input, - }; - } -} diff --git a/apps/backend/src/modules/userModule/tests/factories/userBucketTestFactory/userBucketTestFactory.ts b/apps/backend/src/modules/userModule/tests/factories/userBucketTestFactory/userBucketTestFactory.ts index fca7dc7..adc0ce2 100644 --- a/apps/backend/src/modules/userModule/tests/factories/userBucketTestFactory/userBucketTestFactory.ts +++ b/apps/backend/src/modules/userModule/tests/factories/userBucketTestFactory/userBucketTestFactory.ts @@ -1,6 +1,7 @@ import { Generator } from '@common/tests'; import { type UserBucketDraft, UserBucket } from '../../../domain/entities/userBucket/userBucket.js'; +import { type UserBucketRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userBucketTable/userBucketRawEntity.js'; export class UserBucketTestFactory { public create(input: Partial = {}): UserBucket { @@ -11,4 +12,13 @@ export class UserBucketTestFactory { ...input, }); } + + public createRaw(input: Partial = {}): UserBucketRawEntity { + return { + id: Generator.uuid(), + userId: Generator.uuid(), + bucketName: Generator.word(), + ...input, + }; + } } diff --git a/apps/backend/src/modules/userModule/tests/factories/userEntityTestFactory/userEntityTestFactory.ts b/apps/backend/src/modules/userModule/tests/factories/userEntityTestFactory/userEntityTestFactory.ts deleted file mode 100644 index e33719b..0000000 --- a/apps/backend/src/modules/userModule/tests/factories/userEntityTestFactory/userEntityTestFactory.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { UserRole } from '@common/contracts'; -import { Generator } from '@common/tests'; - -import { type UserRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userTable/userRawEntity.js'; - -export class UserEntityTestFactory { - public create(input: Partial = {}): UserRawEntity { - return { - id: Generator.uuid(), - email: Generator.email().toLowerCase(), - password: Generator.password(), - role: UserRole.user, - ...input, - }; - } -} diff --git a/apps/backend/src/modules/userModule/tests/factories/userTestFactory/userTestFactory.ts b/apps/backend/src/modules/userModule/tests/factories/userTestFactory/userTestFactory.ts index 33ce88d..db663f4 100644 --- a/apps/backend/src/modules/userModule/tests/factories/userTestFactory/userTestFactory.ts +++ b/apps/backend/src/modules/userModule/tests/factories/userTestFactory/userTestFactory.ts @@ -2,6 +2,7 @@ import { UserRole } from '@common/contracts'; import { Generator } from '@common/tests'; import { User, type UserDraft } from '../../../domain/entities/user/user.js'; +import { type UserRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userTable/userRawEntity.js'; export class UserTestFactory { public create(input: Partial = {}): User { @@ -13,4 +14,14 @@ export class UserTestFactory { ...input, }); } + + public createRaw(input: Partial = {}): UserRawEntity { + return { + id: Generator.uuid(), + email: Generator.email().toLowerCase(), + password: Generator.password(), + role: UserRole.user, + ...input, + }; + } } diff --git a/apps/backend/src/modules/userModule/tests/utils/blacklistTokenTestUtils/blacklistTokenTestUtils.ts b/apps/backend/src/modules/userModule/tests/utils/blacklistTokenTestUtils/blacklistTokenTestUtils.ts index 68b4cb5..b856c68 100644 --- a/apps/backend/src/modules/userModule/tests/utils/blacklistTokenTestUtils/blacklistTokenTestUtils.ts +++ b/apps/backend/src/modules/userModule/tests/utils/blacklistTokenTestUtils/blacklistTokenTestUtils.ts @@ -1,19 +1,18 @@ import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; -import { type QueryBuilder } from '../../../../../libs/database/types/queryBuilder.js'; import { type BlacklistTokenRawEntity } from '../../../infrastructure/databases/userDatabase/tables/blacklistTokenTable/blacklistTokenRawEntity.js'; import { BlacklistTokenTable } from '../../../infrastructure/databases/userDatabase/tables/blacklistTokenTable/blacklistTokenTable.js'; import { BlacklistTokenTestFactory } from '../../factories/blacklistTokenTestFactory/blacklistTokenTestFactory.js'; interface CreateAndPersistPayload { - input?: Partial; + readonly input?: Partial; } interface PersistPayload { - blacklistToken: BlacklistTokenRawEntity; + readonly blacklistToken: BlacklistTokenRawEntity; } interface FindByTokenPayload { - token: string; + readonly token: string; } export class BlacklistTokenTestUtils { @@ -22,18 +21,12 @@ export class BlacklistTokenTestUtils { public constructor(private readonly sqliteDatabaseClient: SqliteDatabaseClient) {} - private createQueryBuilder(): QueryBuilder { - return this.sqliteDatabaseClient(this.databaseTable.name); - } - public async createAndPersist(payload: CreateAndPersistPayload = {}): Promise { const { input } = payload; const blacklistToken = this.blacklistTokenTestFactory.create(input); - const queryBuilder = this.createQueryBuilder(); - - const rawEntities = await queryBuilder.insert( + const rawEntities = await this.sqliteDatabaseClient(this.databaseTable.name).insert( { id: blacklistToken.getId(), token: blacklistToken.getToken(), @@ -54,17 +47,13 @@ export class BlacklistTokenTestUtils { public async persist(payload: PersistPayload): Promise { const { blacklistToken } = payload; - const queryBuilder = this.createQueryBuilder(); - - await queryBuilder.insert(blacklistToken, '*'); + await this.sqliteDatabaseClient(this.databaseTable.name).insert(blacklistToken, '*'); } public async findByToken(payload: FindByTokenPayload): Promise { const { token } = payload; - const queryBuilder = this.createQueryBuilder(); - - const blacklistTokenRawEntity = (await queryBuilder + const blacklistTokenRawEntity = (await this.sqliteDatabaseClient(this.databaseTable.name) .select('*') .where({ token }) .first()) as BlacklistTokenRawEntity; @@ -77,8 +66,6 @@ export class BlacklistTokenTestUtils { } public async truncate(): Promise { - const queryBuilder = this.createQueryBuilder(); - - await queryBuilder.truncate(); + await this.sqliteDatabaseClient(this.databaseTable.name).truncate(); } } diff --git a/apps/backend/src/modules/userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.ts b/apps/backend/src/modules/userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.ts new file mode 100644 index 0000000..33100ee --- /dev/null +++ b/apps/backend/src/modules/userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.ts @@ -0,0 +1,67 @@ +import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; +import { type UserBucketRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userBucketTable/userBucketRawEntity.js'; +import { UserBucketTable } from '../../../infrastructure/databases/userDatabase/tables/userBucketTable/userBucketTable.js'; +import { UserBucketTestFactory } from '../../factories/userBucketTestFactory/userBucketTestFactory.js'; + +interface CreateAndPersistPayload { + readonly input?: Partial; +} + +interface FindUserBucketsPayload { + readonly userId: string; +} + +interface FindUserBucketPayload { + readonly bucketName: string; +} + +export class UserBucketTestUtils { + private readonly userBucketTable = new UserBucketTable(); + private readonly userBucketTestFactory = new UserBucketTestFactory(); + + public constructor(private readonly sqliteDatabaseClient: SqliteDatabaseClient) {} + + public async createAndPersist(payload: CreateAndPersistPayload = {}): Promise { + const { input = {} } = payload; + + const userBucket = this.userBucketTestFactory.create(input); + + const queryBuilder = this.sqliteDatabaseClient(this.userBucketTable.name); + + const rawEntities = await queryBuilder.insert( + { + id: userBucket.getId(), + userId: userBucket.getUserId(), + bucketName: userBucket.getBucketName(), + }, + '*', + ); + + return rawEntities[0] as UserBucketRawEntity; + } + + public async findUserBuckets(payload: FindUserBucketsPayload): Promise { + const { userId } = payload; + + const userBuckets = await this.sqliteDatabaseClient(this.userBucketTable.name) + .select('*') + .where({ userId }); + + return userBuckets; + } + + public async findUserBucket(payload: FindUserBucketPayload): Promise { + const { bucketName } = payload; + + const userBucketRawEntity = await this.sqliteDatabaseClient(this.userBucketTable.name) + .select('*') + .where({ bucketName }) + .first(); + + return userBucketRawEntity as UserBucketRawEntity; + } + + public async truncate(): Promise { + await this.sqliteDatabaseClient(this.userBucketTable.name).truncate(); + } +} diff --git a/apps/backend/src/modules/userModule/tests/utils/userTestUtils/userTestUtils.ts b/apps/backend/src/modules/userModule/tests/utils/userTestUtils/userTestUtils.ts index 92291e1..2d051d2 100644 --- a/apps/backend/src/modules/userModule/tests/utils/userTestUtils/userTestUtils.ts +++ b/apps/backend/src/modules/userModule/tests/utils/userTestUtils/userTestUtils.ts @@ -1,61 +1,34 @@ import { type SqliteDatabaseClient } from '../../../../../core/database/sqliteDatabaseClient/sqliteDatabaseClient.js'; -import { type UserTokens } from '../../../domain/entities/userTokens/userTokens.js'; -import { type RefreshTokenRawEntity } from '../../../infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenRawEntity.js'; -import { RefreshTokenTable } from '../../../infrastructure/databases/userDatabase/tables/refreshTokenTable/refreshTokenTable.js'; -import { type UserBucketRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userBucketTable/userBucketRawEntity.js'; -import { UserBucketTable } from '../../../infrastructure/databases/userDatabase/tables/userBucketTable/userBucketTable.js'; import { type UserRawEntity } from '../../../infrastructure/databases/userDatabase/tables/userTable/userRawEntity.js'; import { UserTable } from '../../../infrastructure/databases/userDatabase/tables/userTable/userTable.js'; -import { RefreshTokenTestFactory } from '../../factories/refreshTokenTestFactory/refreshTokenTestFactory.js'; -import { UserBucketTestFactory } from '../../factories/userBucketTestFactory/userBucketTestFactory.js'; -import { UserEntityTestFactory } from '../../factories/userEntityTestFactory/userEntityTestFactory.js'; +import { UserTestFactory } from '../../factories/userTestFactory/userTestFactory.js'; interface CreateAndPersistPayload { - input?: Partial; -} - -interface CreateAndPersistUserBucketPayload { - input?: Partial; -} - -interface CreateAndPersistRefreshTokenPayload { - input?: Partial; + readonly input?: Partial; } interface PersistPayload { - user: UserRawEntity; + readonly user: UserRawEntity; } interface FindByEmailPayload { - email: string; + readonly email: string; } interface FindByIdPayload { - id: string; -} - -interface FindTokensByUserIdPayload { - userId: string; -} - -interface FindBucketsByUserIdPayload { - userId: string; + readonly id: string; } export class UserTestUtils { private readonly userTable = new UserTable(); - private readonly userBucketTable = new UserBucketTable(); - private readonly refreshTokenTable = new RefreshTokenTable(); - private readonly userTestFactory = new UserEntityTestFactory(); - private readonly userBucketTestFactory = new UserBucketTestFactory(); - private readonly refreshTokenTestFactory = new RefreshTokenTestFactory(); + private readonly userTestFactory = new UserTestFactory(); public constructor(private readonly sqliteDatabaseClient: SqliteDatabaseClient) {} public async createAndPersist(payload: CreateAndPersistPayload = {}): Promise { const { input } = payload; - const user = this.userTestFactory.create(input); + const user = this.userTestFactory.createRaw(input); const rawEntities = await this.sqliteDatabaseClient(this.userTable.name).insert( { @@ -70,71 +43,6 @@ export class UserTestUtils { return rawEntities[0] as UserRawEntity; } - public async createAndPersistUserBucket( - payload: CreateAndPersistUserBucketPayload = {}, - ): Promise { - const { input = {} } = payload; - - const userBucket = this.userBucketTestFactory.create(input); - - const queryBuilder = this.sqliteDatabaseClient(this.userBucketTable.name); - - const rawEntities = await queryBuilder.insert( - { - id: userBucket.getId(), - userId: userBucket.getUserId(), - bucketName: userBucket.getBucketName(), - }, - '*', - ); - - return rawEntities[0] as UserBucketRawEntity; - } - - public async createAndPersistRefreshToken( - payload: CreateAndPersistRefreshTokenPayload, - ): Promise { - const { input = {} } = payload; - - const refreshToken = this.refreshTokenTestFactory.create(input); - - const queryBuilder = this.sqliteDatabaseClient(this.refreshTokenTable.name); - - const rawEntities = await queryBuilder.insert( - { - id: refreshToken.getId(), - userId: refreshToken.getUserId(), - token: refreshToken.getToken(), - expiresAt: refreshToken.getExpiresAt(), - }, - '*', - ); - - return rawEntities[0] as RefreshTokenRawEntity; - } - - public async findTokensByUserId(payload: FindTokensByUserIdPayload): Promise { - const { userId } = payload; - - const refreshTokens = await this.sqliteDatabaseClient(this.refreshTokenTable.name) - .select('*') - .where({ userId }); - - return { - refreshTokens: refreshTokens.map((refreshToken) => refreshToken.token), - }; - } - - public async findBucketsByUserId(payload: FindBucketsByUserIdPayload): Promise { - const { userId } = payload; - - const userBuckets = await this.sqliteDatabaseClient(this.userBucketTable.name) - .select('*') - .where({ userId }); - - return userBuckets; - } - public async persist(payload: PersistPayload): Promise { const { user } = payload; @@ -166,10 +74,6 @@ export class UserTestUtils { } public async truncate(): Promise { - await this.sqliteDatabaseClient(this.userBucketTable.name).truncate(); - - await this.sqliteDatabaseClient(this.refreshTokenTable.name).truncate(); - await this.sqliteDatabaseClient(this.userTable.name).truncate(); } } diff --git a/apps/backend/src/modules/userModule/userModule.ts b/apps/backend/src/modules/userModule/userModule.ts index 6634a43..f9c3e01 100644 --- a/apps/backend/src/modules/userModule/userModule.ts +++ b/apps/backend/src/modules/userModule/userModule.ts @@ -25,12 +25,14 @@ import { HashServiceImpl } from './application/services/hashService/hashServiceI import { type PasswordValidationService } from './application/services/passwordValidationService/passwordValidationService.js'; import { PasswordValidationServiceImpl } from './application/services/passwordValidationService/passwordValidationServiceImpl.js'; import { type BlacklistTokenRepository } from './domain/repositories/blacklistTokenRepository/blacklistTokenRepository.js'; +import { type UserBucketRepository } from './domain/repositories/userBucketRepository/userBucketRepository.js'; import { type UserRepository } from './domain/repositories/userRepository/userRepository.js'; import { type BlacklistTokenMapper } from './infrastructure/repositories/blacklistTokenRepository/blacklistTokenMapper/blacklistTokenMapper.js'; import { BlacklistTokenMapperImpl } from './infrastructure/repositories/blacklistTokenRepository/blacklistTokenMapper/blacklistTokenMapperImpl.js'; import { BlacklistTokenRepositoryImpl } from './infrastructure/repositories/blacklistTokenRepository/blacklistTokenRepositoryImpl.js'; -import { type UserBucketMapper } from './infrastructure/repositories/userRepository/userBucketMapper/userBucketMapper.js'; -import { UserBucketMapperImpl } from './infrastructure/repositories/userRepository/userBucketMapper/userBucketMapperImpl.js'; +import { type UserBucketMapper } from './infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapper.js'; +import { UserBucketMapperImpl } from './infrastructure/repositories/userBucketRepository/userBucketMapper/userBucketMapperImpl.js'; +import { UserBucketRepositoryImpl } from './infrastructure/repositories/userBucketRepository/userBucketRepositoryImpl.js'; import { type UserMapper } from './infrastructure/repositories/userRepository/userMapper/userMapper.js'; import { UserMapperImpl } from './infrastructure/repositories/userRepository/userMapper/userMapperImpl.js'; import { UserRepositoryImpl } from './infrastructure/repositories/userRepository/userRepositoryImpl.js'; @@ -63,9 +65,17 @@ export class UserModule implements DependencyInjectionModule { new UserRepositoryImpl( container.get(coreSymbols.sqliteDatabaseClient), container.get(symbols.userMapper), + container.get(coreSymbols.uuidService), + ), + ); + + container.bind( + symbols.userBucketRepository, + () => + new UserBucketRepositoryImpl( + container.get(coreSymbols.sqliteDatabaseClient), container.get(symbols.userBucketMapper), container.get(coreSymbols.uuidService), - container.get(coreSymbols.loggerService), ), ); @@ -108,6 +118,7 @@ export class UserModule implements DependencyInjectionModule { () => new GrantBucketAccessCommandHandlerImpl( container.get(symbols.userRepository), + container.get(symbols.userBucketRepository), container.get(coreSymbols.loggerService), ), ); @@ -117,6 +128,7 @@ export class UserModule implements DependencyInjectionModule { () => new RevokeBucketAccessCommandHandlerImpl( container.get(symbols.userRepository), + container.get(symbols.userBucketRepository), container.get(coreSymbols.loggerService), ), ); @@ -152,6 +164,7 @@ export class UserModule implements DependencyInjectionModule { container.get(authSymbols.tokenService), container.get(symbols.userModuleConfigProvider), container.get(symbols.userRepository), + container.get(symbols.userBucketRepository), container.get(symbols.blacklistTokenRepository), ), ); @@ -177,7 +190,7 @@ export class UserModule implements DependencyInjectionModule { container.bind( symbols.findUserBucketsQueryHandler, - () => new FindUserBucketsQueryHandlerImpl(container.get(symbols.userRepository)), + () => new FindUserBucketsQueryHandlerImpl(container.get(symbols.userBucketRepository)), ); container.bind( diff --git a/apps/backend/tests/container/symbols.ts b/apps/backend/tests/container/symbols.ts index a692eab..d4c859b 100644 --- a/apps/backend/tests/container/symbols.ts +++ b/apps/backend/tests/container/symbols.ts @@ -1,6 +1,6 @@ export const testSymbols = { - applicationService: Symbol('applicationService'), userTestUtils: Symbol('userTestUtils'), + userBucketTestUtils: Symbol('userBucketTestUtils'), blacklistTokenTestUtils: Symbol('blacklistTokenTestUtils'), s3TestUtils: Symbol('s3TestService'), }; diff --git a/apps/backend/tests/container/testContainer.ts b/apps/backend/tests/container/testContainer.ts index dc79d4f..1031b8a 100644 --- a/apps/backend/tests/container/testContainer.ts +++ b/apps/backend/tests/container/testContainer.ts @@ -7,6 +7,7 @@ import { coreSymbols } from '../../src/core/symbols.js'; import { type DependencyInjectionContainer } from '../../src/libs/dependencyInjection/dependencyInjectionContainer.js'; import { S3TestUtils } from '../../src/modules/resourceModule/tests/utils/s3TestUtils.js'; import { BlacklistTokenTestUtils } from '../../src/modules/userModule/tests/utils/blacklistTokenTestUtils/blacklistTokenTestUtils.js'; +import { UserBucketTestUtils } from '../../src/modules/userModule/tests/utils/userBucketTestUtils/userBucketTestUtils.js'; import { UserTestUtils } from '../../src/modules/userModule/tests/utils/userTestUtils/userTestUtils.js'; export class TestContainer { @@ -18,6 +19,11 @@ export class TestContainer { () => new UserTestUtils(container.get(coreSymbols.sqliteDatabaseClient)), ); + container.bind( + testSymbols.userBucketTestUtils, + () => new UserBucketTestUtils(container.get(coreSymbols.sqliteDatabaseClient)), + ); + container.bind( testSymbols.blacklistTokenTestUtils, () => new BlacklistTokenTestUtils(container.get(coreSymbols.sqliteDatabaseClient)), diff --git a/docker-compose.dev.yml b/docker-compose.yml similarity index 100% rename from docker-compose.dev.yml rename to docker-compose.yml From b63ddc8a62652b6332b9a915e27c19a073ddae92 Mon Sep 17 00:00:00 2001 From: Michal Cieslar Date: Wed, 28 Feb 2024 23:24:08 +0100 Subject: [PATCH 2/2] fix gh workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b99a678..09fd7e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,6 @@ jobs: - run: npm ci - run: npm run build:dev - run: npm run test:unit - - run: docker-compose -f docker-compose.dev.yml up -d + - run: docker-compose up -d - run: npm run test:integration # - run: NODE_ENV=ci npm run test:e2e