Skip to content

Commit

Permalink
Merge pull request #17 from VincentJouanne/refactor/domain-functions
Browse files Browse the repository at this point in the history
Refactor/domain functions
  • Loading branch information
VincentJouanne authored Sep 27, 2021
2 parents 0b002c5 + a29267e commit def456a
Show file tree
Hide file tree
Showing 15 changed files with 54 additions and 48 deletions.
2 changes: 1 addition & 1 deletion identity-and-access/__tests__/unit/signIn.command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { executeTask } from '@common/utils/executeTask';
import { SignIn, SignInHandler } from '@identity-and-access/application/commands/signIn.command';
import { IncorrectPasswordException } from '@identity-and-access/domain/exceptions/incorrectPassword.exception';
import { UserNotFoundException } from '@identity-and-access/domain/exceptions/userNotFound.exception';
import { UserRepository } from '@identity-and-access/domain/repositories/user.repository';
import { FakeUserRepository } from '@identity-and-access/infrastructure/adapters/secondaries/fake/fakeUser.repository';
import { RealAuthenticationService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realAuthentication.service';
import { RealHashingService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realHashing.service';
import { RealRandomNumberGenerator } from '@identity-and-access/infrastructure/adapters/secondaries/real/realRandomNumberGenerator';
import { UserRepository } from '@identity-and-access/infrastructure/ports/user.repository';
import { UnprocessableEntityException } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { Test } from '@nestjs/testing';
Expand Down
2 changes: 1 addition & 1 deletion identity-and-access/__tests__/unit/signUp.command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { PinoLoggerService } from '@common/logger/adapters/real/pinoLogger.servi
import { executeTask } from '@common/utils/executeTask';
import { SignUp, SignUpHandler } from '@identity-and-access/application/commands/signUp.command';
import { EmailAlreadyExistsException } from '@identity-and-access/domain/exceptions/emailAlreadyExists.exception';
import { UserRepository } from '@identity-and-access/domain/repositories/user.repository';
import { FakeUserRepository } from '@identity-and-access/infrastructure/adapters/secondaries/fake/fakeUser.repository';
import { RealHashingService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realHashing.service';
import { RealRandomNumberGenerator } from '@identity-and-access/infrastructure/adapters/secondaries/real/realRandomNumberGenerator';
import { RealUUIDGeneratorService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realUUIDGenerator.service';
import { UserRepository } from '@identity-and-access/infrastructure/ports/user.repository';
import { UnprocessableEntityException } from '@nestjs/common';
import { Test } from '@nestjs/testing';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { executeTask } from "@common/utils/executeTask";
import { VerifyEmail, VerifyEmailHandler } from "@identity-and-access/application/commands/verifyEmail.command";
import { IncorrectVerificationCodeException } from "@identity-and-access/domain/exceptions/incorrectVerificationCode.exception";
import { UserNotFoundException } from "@identity-and-access/domain/exceptions/userNotFound.exception";
import { UserRepository } from "@identity-and-access/domain/repositories/user.repository";
import { FakeUserRepository } from "@identity-and-access/infrastructure/adapters/secondaries/fake/fakeUser.repository";
import { UserRepository } from "@identity-and-access/infrastructure/ports/user.repository";
import { UnprocessableEntityException } from "@nestjs/common/exceptions/unprocessable-entity.exception";
import { Test } from "@nestjs/testing/test";
import { UserBuilder } from "../data-builders/userBuilder";
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('[Unit] Verify email with verification code', () => {
await expect(resultPromise).rejects.toBeInstanceOf(UnprocessableEntityException);
})


it('Should throw UserNotFoundException if user does not exists', async () => {
//Given an existing user
const user = UserBuilder().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import { perform } from '@common/utils/perform';
import { User } from '@identity-and-access/domain/entities/user';
import { IncorrectPasswordException } from '@identity-and-access/domain/exceptions/incorrectPassword.exception';
import { UserNotFoundException } from '@identity-and-access/domain/exceptions/userNotFound.exception';
import { UserRepository } from '@identity-and-access/domain/repositories/user.repository';
import { AccessToken } from '@identity-and-access/domain/value-objects/accessToken';
import { PlainPassword } from '@identity-and-access/domain/value-objects/password';
import { RefreshToken } from '@identity-and-access/domain/value-objects/refreshToken';
import { RealAuthenticationService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realAuthentication.service';
import { RealHashingService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realHashing.service';
import { UserRepository } from '@identity-and-access/infrastructure/ports/user.repository';
import { CommandHandler, ICommand, ICommandHandler } from '@nestjs/cqrs';
import { Email } from '@notifications/domain/value-objects/email';
import { sequenceS, sequenceT } from 'fp-ts/lib/Apply';
import { pipe } from 'fp-ts/lib/function';
import { chain, left, right, taskEither } from 'fp-ts/lib/TaskEither';

export class SignIn implements ICommand {
constructor(public readonly email: string, public readonly password: string) {}
constructor(public readonly email: string, public readonly password: string) { }
}

@CommandHandler(SignIn)
Expand Down
10 changes: 5 additions & 5 deletions identity-and-access/src/application/commands/signUp.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ import { perform } from '@common/utils/perform';
import { User, UserId } from '@identity-and-access/domain/entities/user';
import { USER_REGISTERED } from '@identity-and-access/domain/events/userRegistered.event';
import { EmailAlreadyExistsException } from '@identity-and-access/domain/exceptions/emailAlreadyExists.exception';
import { UserRepository } from '@identity-and-access/domain/repositories/user.repository';
import { PlainPassword } from '@identity-and-access/domain/value-objects/password';
import { RealHashingService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realHashing.service';
import { RealRandomNumberGenerator } from '@identity-and-access/infrastructure/adapters/secondaries/real/realRandomNumberGenerator';
import { RealUUIDGeneratorService } from '@identity-and-access/infrastructure/adapters/secondaries/real/realUUIDGenerator.service';
import { UserRepository } from '@identity-and-access/infrastructure/ports/user.repository';
import { CommandHandler, ICommand, ICommandHandler } from '@nestjs/cqrs';
import { Email } from '@notifications/domain/value-objects/email';
import { sequenceS, sequenceT } from 'fp-ts/lib/Apply';
import { pipe } from 'fp-ts/lib/function';
import { chain, left, map, right, taskEither } from 'fp-ts/lib/TaskEither';

export class SignUp implements ICommand {
constructor(public readonly email: string, public readonly password: string) {}
constructor(public readonly email: string, public readonly password: string) { }
}

@CommandHandler(SignUp)
export class SignUpHandler implements ICommandHandler {
constructor(
private readonly uuidGeneratorService: RealUUIDGeneratorService,
private readonly randomNumberGenerator: RealRandomNumberGenerator,
private readonly randomNumberGenerator: RealRandomNumberGenerator,
private readonly hashingService: RealHashingService,
private readonly userRepository: UserRepository,
private readonly domainEventPublisher: DomainEventPublisher,
Expand Down Expand Up @@ -82,7 +82,7 @@ export class SignUpHandler implements ICommandHandler {
//Store entity
chain((user) => sequenceT(taskEither)(perform(user, this.userRepository.save, this.logger, 'save user in storage system.'), right(user))),
//Emit domain event
chain(([nothing, user]) =>
chain(([_, user]) =>
perform(
{ eventKey: USER_REGISTERED, payload: { email: user.contactInformation.email, verificationCode: user.contactInformation.verificationCode } },
this.domainEventPublisher.publishEvent,
Expand All @@ -96,4 +96,4 @@ export class SignUpHandler implements ICommandHandler {
}
}

const noop = () => {};
const noop = () => { };
34 changes: 16 additions & 18 deletions identity-and-access/src/application/commands/verifyEmail.command.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { ICommand, CommandHandler, ICommandHandler } from "@nestjs/cqrs";
import { executeTask } from "@common/utils/executeTask";
import { pipe } from "fp-ts/lib/function";
import { UserRepository } from "@identity-and-access/domain/repositories/user.repository";
import { PinoLoggerService } from "@common/logger/adapters/real/pinoLogger.service";
import { perform } from "@common/utils/perform";
import { executeTask } from "@common/utils/executeTask";
import { fromUnknown } from "@common/utils/fromUnknown";
import { noop } from "@common/utils/noop";
import { map, chain, left, right, taskEither } from "fp-ts/lib/TaskEither";
import { UserNotFoundException } from "@identity-and-access/domain/exceptions/userNotFound.exception";
import { User, UserId } from "@identity-and-access/domain/entities/user";
import { perform } from "@common/utils/perform";
import { UserId, verifyUserEmail } from "@identity-and-access/domain/entities/user";
import { IncorrectVerificationCodeException } from "@identity-and-access/domain/exceptions/incorrectVerificationCode.exception";
import { fromUnknown } from "@common/utils/fromUnknown";
import { UserNotFoundException } from "@identity-and-access/domain/exceptions/userNotFound.exception";
import { VerificationCode4 } from "@identity-and-access/domain/value-objects/verificationCode4";
import { UserRepository } from "@identity-and-access/infrastructure/ports/user.repository";
import { CommandHandler, ICommand, ICommandHandler } from "@nestjs/cqrs";
import { sequenceS, sequenceT } from "fp-ts/lib/Apply";
import { pipe } from "fp-ts/lib/function";
import { chain, left, map, right, taskEither } from "fp-ts/lib/TaskEither";

export class VerifyEmail implements ICommand {
constructor(public readonly userId: string, public readonly verificationCode: string) {}
}
constructor(public readonly userId: string, public readonly verificationCode: string) { }
}

@CommandHandler(VerifyEmail)
export class VerifyEmailHandler implements ICommandHandler {
Expand All @@ -30,20 +30,18 @@ export class VerifyEmailHandler implements ICommandHandler {
userId: fromUnknown(command.userId, UserId, this.logger, 'user id'),
verificationCode: fromUnknown(command.verificationCode, VerificationCode4, this.logger, 'verification code')
}),
chain((validatedDatas) =>
sequenceT(taskEither)(perform(validatedDatas.userId, this.userRepository.getById, this.logger, 'get user by id'), right(validatedDatas))
chain((validatedDatas) =>
sequenceT(taskEither)(perform(validatedDatas.userId, this.userRepository.getById, this.logger, 'get user by id'), right(validatedDatas))
),
chain(([user, validatedDatas]) => {
if(user == null)
if (user == null)
return left(new UserNotFoundException());
else if (validatedDatas.verificationCode != user.contactInformation.verificationCode)
return left(new IncorrectVerificationCodeException());
else
else
return right(user)
}),
chain((user) =>
fromUnknown({...user, contactInformation: { ...user.contactInformation, isVerified: true }}, User, this.logger, 'user' )
),
chain((user) => verifyUserEmail(user, this.logger)),
chain((user) => perform(user, this.userRepository.save, this.logger, 'save user ')),
map(noop)
)
Expand Down
10 changes: 9 additions & 1 deletion identity-and-access/src/domain/entities/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { fromUnknown } from '@common/utils/fromUnknown';
import { UUID } from '@identity-and-access/domain/value-objects/uuid';
import { LoggerService } from '@nestjs/common';
import { TaskEither } from 'fp-ts/lib/TaskEither';
import { Record, Static } from 'runtypes';
import { HashedPassword } from '../value-objects/password';
import { ContactInformation } from '../value-objects/contactInformation';
import { HashedPassword } from '../value-objects/password';

export const UserId = UUID.withBrand('UserId');
export type UserId = Static<typeof UserId>;
Expand All @@ -12,3 +15,8 @@ export const User = Record({
contactInformation: ContactInformation,
});
export type User = Static<typeof User>;


export const verifyUserEmail = (user: User, logger: LoggerService): TaskEither<Error, User> => {
return fromUnknown({ ...user, contactInformation: { ...user.contactInformation, isVerified: true } }, User, logger, 'user')
}
2 changes: 1 addition & 1 deletion identity-and-access/src/identityAndAccess.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { RefreshTokensHandler } from './application/commands/refreshTokens.comma
import { SignInHandler } from './application/commands/signIn.command';
import { SignUpHandler } from './application/commands/signUp.command';
import { VerifyEmailHandler } from './application/commands/verifyEmail.command';
import { UserRepository } from './domain/repositories/user.repository';
import { AuthenticationController } from './infrastructure/adapters/primaries/controllers/authentication.controller';
import { UsersController } from './infrastructure/adapters/primaries/controllers/users.controller';
import { RealAuthenticationService } from './infrastructure/adapters/secondaries/real/realAuthentication.service';
import { RealHashingService } from './infrastructure/adapters/secondaries/real/realHashing.service';
import { RealRandomNumberGenerator } from './infrastructure/adapters/secondaries/real/realRandomNumberGenerator';
import { RealUserRepository } from './infrastructure/adapters/secondaries/real/realUser.repository';
import { RealUUIDGeneratorService } from './infrastructure/adapters/secondaries/real/realUUIDGenerator.service';
import { UserRepository } from './infrastructure/ports/user.repository';

const controllers = [AuthenticationController, UsersController]
const commandHandlers = [SignUpHandler, SignInHandler, RefreshTokensHandler, VerifyEmailHandler];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { executeTask } from '@common/utils/executeTask';
import { User, UserId } from '@identity-and-access/domain/entities/user';
import { UserRepository } from '@identity-and-access/domain/repositories/user.repository';
import { UserRepository } from '@identity-and-access/infrastructure/ports/user.repository';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { Email } from '@notifications/domain/value-objects/email';
import { TaskEither, tryCatch } from 'fp-ts/lib/TaskEither';
Expand Down Expand Up @@ -35,11 +35,11 @@ export class FakeUserRepository implements UserRepository {
return tryCatch(
async () => {
const existingUser = await executeTask(this.getById(user.id))
if(existingUser != null) {
const index = this.users.findIndex(userInStorage => userInStorage.id == user.id)
if (existingUser != null) {
const index = this.users.findIndex(userInStorage => userInStorage.id == user.id)
this.users.splice(index, 1)
}
this.users.push(user);
this.users.push(user);
},
(reason: unknown) => new InternalServerErrorException(),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PinoLoggerService } from '@common/logger/adapters/real/pinoLogger.service';
import { UserId } from '@identity-and-access/domain/entities/user';
import { InvalidTokenException } from '@identity-and-access/domain/exceptions/invalidToken.exception';
import { AuthenticationService } from '@identity-and-access/domain/services/authentication.service';
import { AccessToken } from '@identity-and-access/domain/value-objects/accessToken';
import { jwtConstants } from '@identity-and-access/domain/value-objects/constants';
import { RefreshToken } from '@identity-and-access/domain/value-objects/refreshToken';
import { AuthenticationService } from '@identity-and-access/infrastructure/ports/authentication.service';
import { Injectable, InternalServerErrorException, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
Expand All @@ -20,7 +20,7 @@ export class RealAuthenticationService implements AuthenticationService {
return tryCatch(
async () => {
const payload = { id: userId };
return [AccessToken.check(this.jwtService.sign(payload, {secret: jwtConstants.access_token_secret, expiresIn: jwtConstants.access_token_expiry})), RefreshToken.check(this.jwtService.sign(payload, {secret: jwtConstants.refresh_token_secret, expiresIn: jwtConstants.refresh_token_expiry}))];
return [AccessToken.check(this.jwtService.sign(payload, { secret: jwtConstants.access_token_secret, expiresIn: jwtConstants.access_token_expiry })), RefreshToken.check(this.jwtService.sign(payload, { secret: jwtConstants.refresh_token_secret, expiresIn: jwtConstants.refresh_token_expiry }))];
},
(reason: unknown) => new InternalServerErrorException(),
);
Expand All @@ -45,7 +45,7 @@ export class RealAuthenticationService implements AuthenticationService {
}
return right(bearerMatch.groups.token);
};

verifyIntegrityAndAuthenticityOfAccessToken = (accessToken: string) => {
return pipe(
accessToken,
Expand Down Expand Up @@ -77,7 +77,7 @@ export class RealAuthenticationService implements AuthenticationService {
verifyAccessToken(accessToken: string): TaskEither<Error, void> {
return pipe(
tryCatch(
async () => await this.jwtService.verify(accessToken, {secret: jwtConstants.access_token_secret}),
async () => await this.jwtService.verify(accessToken, { secret: jwtConstants.access_token_secret }),
(error) => new Error(`Verification failed: ${error}`),
),
map((decodedToken) => {
Expand All @@ -90,7 +90,7 @@ export class RealAuthenticationService implements AuthenticationService {
verifyRefreshToken(refreshToken: string): TaskEither<Error, string> {
return pipe(
tryCatch(
async () => await this.jwtService.verify(refreshToken, {secret: jwtConstants.refresh_token_secret}),
async () => await this.jwtService.verify(refreshToken, { secret: jwtConstants.refresh_token_secret }),
(error) => new Error(`Verification failed: ${error}`),
),
map((decodedRefreshToken) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PinoLoggerService } from '@common/logger/adapters/real/pinoLogger.service';
import { HashingService } from '@identity-and-access/domain/services/hashing.service';
import { HashedPassword, PlainPassword } from '@identity-and-access/domain/value-objects/password';
import { HashingService } from '@identity-and-access/infrastructure/ports/hashing.service';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { TaskEither, tryCatch } from 'fp-ts/lib/TaskEither';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { PrismaService } from '@common/prisma/adapters/prisma.service';
import { User, UserId } from '@identity-and-access/domain/entities/user';
import { UserRepository } from '@identity-and-access/domain/repositories/user.repository';
import { ContactInformation } from '@identity-and-access/domain/value-objects/contactInformation';
import { UserRepository } from '@identity-and-access/infrastructure/ports/user.repository';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { Email } from '@notifications/domain/value-objects/email';
import { TaskEither, tryCatch } from 'fp-ts/lib/TaskEither';

@Injectable()
export class RealUserRepository implements UserRepository {
constructor(private prisma: PrismaService) {}
constructor(private prisma: PrismaService) { }

getById = (userId: UserId) : TaskEither<Error, User | null> => {
getById = (userId: UserId): TaskEither<Error, User | null> => {
return tryCatch(
async () => {
const prismaUser = await this.prisma.user.findUnique({
Expand Down
Loading

0 comments on commit def456a

Please sign in to comment.