Skip to content

Commit

Permalink
chore: improves example codes
Browse files Browse the repository at this point in the history
  • Loading branch information
jaksonxavier committed May 11, 2023
1 parent b1475fc commit 9c91aa1
Show file tree
Hide file tree
Showing 31 changed files with 467 additions and 88 deletions.
18 changes: 0 additions & 18 deletions src/application/use-cases/example-use-case.spec.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/application/use-cases/example-use-case.ts

This file was deleted.

12 changes: 12 additions & 0 deletions src/application/use-cases/user-case.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';

import { DatabaseModule } from '@infra/database/database.module';

import { GetUserByEmailUseCase } from './user/get-user-by-email.use-case';

@Module({
imports: [DatabaseModule],
providers: [GetUserByEmailUseCase],
exports: [GetUserByEmailUseCase],
})
export class UseCasesModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DomainError } from '@core/domain/errors/DomainError';

export class UserByEmailNotFoundError extends Error implements DomainError {
constructor(email: string) {
super(`User with email '${email}' was not found.`);
this.name = 'UserNotFound';
}
}
50 changes: 50 additions & 0 deletions src/application/use-cases/user/get-user-by-email.use-case.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { EmailBadFormattedError } from '@domain/value-objects/errors/email-bad-formatted-error';

import { UsersRepository } from '@infra/database/repositories/users.repository';

import { makeFakeUser } from '@test/factories/users.factory';
import { InMemoryUsersRepository } from '@test/repositories/in-memory-users.repository';

import { UserByEmailNotFoundError } from './errors/user-by-email-not-found.error';
import { GetUserByEmailUseCase } from './get-user-by-email.use-case';

describe('GetUserByEmailUseCase', () => {
let usersRepository: UsersRepository;

let getUserByEmailUseCase: GetUserByEmailUseCase;

beforeEach(() => {
usersRepository = new InMemoryUsersRepository();

getUserByEmailUseCase = new GetUserByEmailUseCase(usersRepository);
});

it('should be able to get user by email', async () => {
const user = makeFakeUser();

await usersRepository.create(user);

const output = await getUserByEmailUseCase.handle(user.email);

expect(output.isRight()).toBeTruthy();
expect(output.value).toEqual(user);
});

it('should be able an error is returned in case an invalid email address is provided', async () => {
const invalidEmail = 'invalid_email';

const output = await getUserByEmailUseCase.handle(invalidEmail);

expect(output.isLeft()).toBeTruthy();
expect(output.value).toBeInstanceOf(EmailBadFormattedError);
});

it('should be able to return user not found error', async () => {
const email = '[email protected]';

const output = await getUserByEmailUseCase.handle(email);

expect(output.isLeft()).toBeTruthy();
expect(output.value).toBeInstanceOf(UserByEmailNotFoundError);
});
});
37 changes: 37 additions & 0 deletions src/application/use-cases/user/get-user-by-email.use-case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';

import { Either, left, right } from '@core/logic/Either';

import { User } from '@domain/entities/user.entity';
import { Email } from '@domain/value-objects/email';
import { EmailBadFormattedError } from '@domain/value-objects/errors/email-bad-formatted-error';

import { UsersRepository } from '@infra/database/repositories/users.repository';

import { UserByEmailNotFoundError } from './errors/user-by-email-not-found.error';

type GetUserByEmailResponse = Either<
EmailBadFormattedError | UserByEmailNotFoundError,
User
>;

@Injectable()
export class GetUserByEmailUseCase {
constructor(private readonly usersRepository: UsersRepository) {}

async handle(email: string): Promise<GetUserByEmailResponse> {
const isInvalidEmail = !Email.validate(email);

if (isInvalidEmail) {
return left(new EmailBadFormattedError(email));
}

const user = await this.usersRepository.findByEmail(email);

if (!user) {
return left(new UserByEmailNotFoundError(email));
}

return right(user);
}
}
4 changes: 2 additions & 2 deletions src/config/database.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dotenv/config';

export const USER = process.env.DATABASE_USER ?? 'docker';
export const PASSWORD = process.env.DATABASE_PASS ?? 'docker';
export const USER = process.env.DATABASE_USER ?? 'root';
export const PASSWORD = process.env.DATABASE_PASS ?? 'toor';
export const HOST = process.env.DATABASE_HOST ?? 'localhost';
export const PORT = process.env.DATABASE_PORT ?? '3306';
export const NAME = process.env.DATABASE_NAME ?? 'app';
14 changes: 14 additions & 0 deletions src/core/logic/Replace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Replaces the property definitions from a given type
* @example
* ```typescript
* type Post {
* id: string;
* name: string;
* }
*
* Replace<Post, {id: number}>
* ```
**/

export type Replace<T, R> = Omit<T, keyof R> & R;
Empty file removed src/domain/entities/.gitkeep
Empty file.
37 changes: 37 additions & 0 deletions src/domain/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Entity } from '@core/domain/Entity';
import { Replace } from '@core/logic/Replace';

export type UserProps = {
email: string;
createdAt: Date;
};

export class User extends Entity<UserProps> {
get email() {
return this.props.email;
}

get createdAt() {
return this.props.createdAt;
}

static create(
props: Replace<
UserProps,
{
createdAt?: Date;
}
>,
id?: string,
) {
const user = new User(
{
...props,
createdAt: props.createdAt ?? new Date(),
},
id,
);

return user;
}
}
27 changes: 27 additions & 0 deletions src/domain/value-objects/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Either, left, right } from '@core/logic/Either';

import { EmailBadFormattedError } from './errors/email-bad-formatted-error';

export class Email {
protected constructor(private readonly email: string) {}

get value(): string {
return this.email;
}

static validate(email: string): boolean {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

return emailRegex.test(email);
}

static create(email: string): Either<EmailBadFormattedError, Email> {
const isValidEmail = this.validate(email);

if (!isValidEmail) {
return left(new EmailBadFormattedError(email));
}

return right(new Email(email));
}
}
8 changes: 8 additions & 0 deletions src/domain/value-objects/errors/email-bad-formatted-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DomainError } from '@core/domain/errors/DomainError';

export class EmailBadFormattedError extends Error implements DomainError {
constructor(email: string) {
super(`The email '${email}' is bad formatted.`);
this.name = 'EmailBadFormatted';
}
}
12 changes: 10 additions & 2 deletions src/infra/database/database.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { Module } from '@nestjs/common';

import { PrismaService } from './prisma/prisma.service';
import { PrismaUsersRepository } from './prisma/repositories/prisma-users-repository';
import { UsersRepository } from './repositories/users.repository';

@Module({
providers: [PrismaService],
exports: [PrismaService],
providers: [
PrismaService,
{
provide: UsersRepository,
useClass: PrismaUsersRepository,
},
],
exports: [PrismaService, UsersRepository],
})
export class DatabaseModule {}
Empty file.
22 changes: 22 additions & 0 deletions src/infra/database/prisma/mappers/user.mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Prisma, User as RawUser } from '@prisma/client';

import { User } from '@domain/entities/user.entity';

export class UserMapper {
static toDomain(raw: RawUser): User {
const user = User.create({
email: raw.email,
createdAt: raw.created_at,
});

return user;
}

static toPersistence(user: User): Prisma.UserCreateInput {
return {
id: user.id,
email: user.email,
created_at: user.createdAt,
};
}
}
Empty file.
37 changes: 37 additions & 0 deletions src/infra/database/prisma/repositories/prisma-users-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';

import { AsyncMaybe } from '@core/logic/Maybe';

import { User } from '@domain/entities/user.entity';

import { UsersRepository } from '@infra/database/repositories/users.repository';

import { UserMapper } from '../mappers/user.mapper';
import { PrismaService } from '../prisma.service';

@Injectable()
export class PrismaUsersRepository implements UsersRepository {
constructor(private readonly prisma: PrismaService) {}

async create(user: User): Promise<User> {
await this.prisma.user.create({
data: UserMapper.toPersistence(user),
});

return user;
}

async findByEmail(email: string): AsyncMaybe<User> {
const user = await this.prisma.user.findUnique({
where: {
email,
},
});

if (!user) {
return null;
}

return UserMapper.toDomain(user);
}
}
Empty file.
11 changes: 11 additions & 0 deletions src/infra/database/repositories/users.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';

import { AsyncMaybe } from '@core/logic/Maybe';

import { User } from '@domain/entities/user.entity';

@Injectable()
export abstract class UsersRepository {
abstract create(user: User): Promise<User>;
abstract findByEmail(email: string): AsyncMaybe<User>;
}
Empty file.
19 changes: 19 additions & 0 deletions src/infra/http/graphql/dto/models/user.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@nestjs/common';
import { Field, ID, ObjectType } from '@nestjs/graphql';

import { Paginated } from '../../common/dto/models/paginated';

@ObjectType()
export class User {
@Field(() => ID)
id: string;

@Field(() => String)
email: string;

@Field(() => Date)
createdAt: Date;
}

@Injectable()
export class PaginatedUsers extends Paginated(User) {}
36 changes: 0 additions & 36 deletions src/infra/http/graphql/resolvers/example.resolver.e2e-spec.ts

This file was deleted.

Loading

0 comments on commit 9c91aa1

Please sign in to comment.