Skip to content

Commit

Permalink
feat(course): adding relations at the course
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardodimarchi committed Oct 13, 2023
1 parent 3731589 commit 136b718
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { faker } from '@faker-js/faker';
import { Right } from '@shared/helpers/either';
import { CourseEntity } from './course.entity';
import { UUID } from 'crypto';
import { MockUser } from 'test/factories/mock-user';
import { CourseEntity } from './course.entity';

describe('CourseEntity', () => {
it('should be able to instantiate', () => {
const title = 'Course title';
const description = 'Course description';
const instructorId = faker.string.uuid() as UUID;
const instructor = MockUser.createEntity();
const price = 50;

const entity = CourseEntity.create({
title,
description,
price,
instructorId,
instructor,
});

expect(entity).toBeInstanceOf(Right);

expect((entity.value as CourseEntity).title).toEqual(title);
expect((entity.value as CourseEntity).description).toEqual(description);
expect((entity.value as CourseEntity).instructorId).toEqual(instructorId);
expect((entity.value as CourseEntity).instructor).toEqual(instructor);
expect((entity.value as CourseEntity).price.amount).toEqual(price);
});
Expand Down
35 changes: 22 additions & 13 deletions src/modules/course/domain/entities/course/course.entity.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { BaseEntity, BaseEntityProps } from '@shared/domain/base.entity';
import { Replace } from '@shared/helpers/replace';
import { Either, Left, Right } from '@shared/helpers/either';
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { Money } from './value-objects/money';
import { Replace } from '@shared/helpers/replace';
import { UUID } from 'crypto';
import { InvalidMoneyError } from '../../errors/invalid-money.error';
import { Money } from './value-objects/money';

export interface CourseEntityProps {
title: string;
description: string;
price: Money;
instructor: UserEntity;
instructorId: UUID;
instructor?: UserEntity;
}

export type CourseEntityCreateProps = Replace<
Expand All @@ -22,15 +24,21 @@ export type CourseEntityCreateProps = Replace<
export class CourseEntity extends BaseEntity<CourseEntityProps> {
private constructor(
props: CourseEntityProps,
baseEntityProps?: BaseEntityProps
baseEntityProps?: BaseEntityProps,
) {
super(props, baseEntityProps);
Object.freeze(this);
}

static create(
{ title, description, price, instructor }: CourseEntityCreateProps,
baseEntityProps?: BaseEntityProps
{
title,
description,
price,
instructorId,
instructor,
}: CourseEntityCreateProps,
baseEntityProps?: BaseEntityProps,
): Either<InvalidMoneyError, CourseEntity> {
const priceValue = Money.create(price);

Expand All @@ -44,10 +52,11 @@ export class CourseEntity extends BaseEntity<CourseEntityProps> {
title,
description,
price: priceValue.value,
instructorId,
instructor,
},
baseEntityProps
)
baseEntityProps,
),
);
}

Expand Down Expand Up @@ -75,11 +84,11 @@ export class CourseEntity extends BaseEntity<CourseEntityProps> {
this.props.price = price;
}

public get instructor(): UserEntity {
return this.props.instructor;
public get instructorId(): UUID {
return this.props.instructorId;
}

public set instructor(instructor: UserEntity) {
this.props.instructor = instructor;
public get instructor(): UserEntity | null {
return this.props.instructor || null;
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { faker } from '@faker-js/faker';
import { Right } from '@shared/helpers/either';
import { EnrollmentEntity } from './enrollment.entity';
import { MockUser } from 'test/factories/mock-user';
import { UUID } from 'crypto';
import { MockCourse } from 'test/factories/mock-course';
import { MockUser } from 'test/factories/mock-user';
import { EnrollmentEntity } from './enrollment.entity';

describe('EnrollmentEntity', () => {
it('should be able to instantiate', () => {
const studentId = faker.string.uuid() as UUID;
const courseId = faker.string.uuid() as UUID;
const student = MockUser.createEntity();
const course = MockCourse.createEntity();

const entity = EnrollmentEntity.create({
studentId,
courseId,
student,
course,
});

expect(entity).toBeInstanceOf(Right);

expect((entity.value as EnrollmentEntity).studentId).toEqual(studentId);
expect((entity.value as EnrollmentEntity).courseId).toEqual(courseId);
expect((entity.value as EnrollmentEntity).student).toEqual(student);
expect((entity.value as EnrollmentEntity).course).toEqual(course);
});
Expand Down
26 changes: 13 additions & 13 deletions src/modules/course/domain/usecases/create-course.usecase.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { faker } from '@faker-js/faker';
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { UUID } from 'crypto';
import { MockUser } from 'test/factories/mock-user';
import { InMemoryCourseRepository } from 'test/repositories/in-memory-course-repository';
import { InMemoryRepository } from 'test/repositories/in-memory-repository';
import { InMemoryUserRepository } from 'test/repositories/in-memory-user-repository';
import { CourseEntity } from '../entities/course/course.entity';
import { InstructorNotFoundError } from '../errors/instructor-not-found.error';
import { CourseRepository } from '../repositories/course.repository';
import {
CreateCourseUseCase,
CreateCourseUseCaseOutput,
} from './create-course.usecase';
import { CourseEntity } from '../entities/course/course.entity';
import { InMemoryRepository } from 'test/repositories/in-memory-repository';
import { CourseRepository } from '../repositories/course.repository';
import { InMemoryCourseRepository } from 'test/repositories/in-memory-course-repository';
import { InstructorNotFoundError } from '../errors/instructor-not-found.error';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { InMemoryUserRepository } from 'test/repositories/in-memory-user-repository';
import { UUID } from 'crypto';
import { MockUser } from 'test/factories/mock-user';

describe('CreateCourseUseCase', () => {
let usecase: CreateCourseUseCase;
Expand All @@ -27,7 +27,7 @@ describe('CreateCourseUseCase', () => {

const createInstructorWithId = (instructorId: UUID) => {
userRepository.save(
MockUser.createEntity({ basePropsOverride: { id: instructorId } })
MockUser.createEntity({ basePropsOverride: { id: instructorId } }),
);
};

Expand All @@ -44,7 +44,7 @@ describe('CreateCourseUseCase', () => {

expect(result.isRight()).toBeTruthy();
expect(
(result.value as CreateCourseUseCaseOutput).createdCourse
(result.value as CreateCourseUseCaseOutput).createdCourse,
).toBeInstanceOf(CourseEntity);
});

Expand All @@ -60,7 +60,7 @@ describe('CreateCourseUseCase', () => {
});

expect(
(result.value as CreateCourseUseCaseOutput).createdCourse.instructor.id
(result.value as CreateCourseUseCaseOutput).createdCourse.instructorId,
).toEqual(instructorId);
});

Expand Down
16 changes: 9 additions & 7 deletions src/modules/course/domain/usecases/create-course.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { UseCase } from '@shared/domain/usecase';
import { CourseEntity } from '../entities/course/course.entity';
import { Either, Left, Right } from '@shared/helpers/either';
import { InvalidMoneyError } from '../errors/invalid-money.error';
import { CourseRepository } from '../repositories/course.repository';
import { UUID } from 'crypto';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { CourseEntity } from '../entities/course/course.entity';
import { InstructorNotFoundError } from '../errors/instructor-not-found.error';
import { InvalidMoneyError } from '../errors/invalid-money.error';
import { CourseRepository } from '../repositories/course.repository';

export interface CreateCourseUseCaseInput {
title: string;
Expand All @@ -18,7 +18,9 @@ export interface CreateCourseUseCaseOutput {
createdCourse: CourseEntity;
}

export type CreateCourseUseCaseErrors = InvalidMoneyError | InstructorNotFoundError;
export type CreateCourseUseCaseErrors =
| InvalidMoneyError
| InstructorNotFoundError;

export class CreateCourseUseCase
implements
Expand All @@ -30,7 +32,7 @@ export class CreateCourseUseCase
{
constructor(
private readonly repository: CourseRepository,
private readonly userRepository: UserRepository
private readonly userRepository: UserRepository,
) {}

async exec({
Expand All @@ -51,7 +53,7 @@ export class CreateCourseUseCase
title,
description,
price,
instructor,
instructorId,
});

if (courseResult.isLeft()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import { CourseEntity } from '@modules/course/domain/entities/course/course.entity';
import { CourseSchema } from '../schemas/course.schema';
import { TypeOrmUserMapper } from '@modules/user/infra/database/typeorm/mappers/typeorm-user.mapper';
import { CourseSchema } from '../schemas/course.schema';

export class TypeOrmCourseMapper {
static toEntity(schema: CourseSchema): CourseEntity {
const course = CourseEntity.create(
{
title: schema.title,
description: schema.description,
price: schema.price,
instructor: TypeOrmUserMapper.toEntity(schema.instructor),
title: schema.title,
description: schema.description,
price: schema.price,
instructorId: schema.instructorId,
...(schema.instructor && {
instructor: TypeOrmUserMapper.toEntity(schema.instructor),
}),
},
{
id: schema.id,
createdAt: schema.createdAt,
updatedAt: schema.updatedAt,
}
},
);

if (course.isLeft()) {
throw new Error(`Could not map course schema to entity: ${ course.value }`);
throw new Error(`Could not map course schema to entity: ${course.value}`);
}

return course.value;
Expand All @@ -31,7 +34,7 @@ export class TypeOrmCourseMapper {
title: entity.title,
description: entity.description,
price: entity.price.amount,
instructor: TypeOrmUserMapper.toSchema(entity.instructor),
instructorId: entity.instructorId,
});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UserSchema } from "@modules/user/infra/database/typeorm/schemas/user.schema";
import { BaseSchema } from "@shared/infra/database/typeorm/base.schema";
import { Column, Entity, ManyToOne } from "typeorm";
import { UserSchema } from '@modules/user/infra/database/typeorm/schemas/user.schema';
import { BaseSchema } from '@shared/infra/database/typeorm/base.schema';
import { UUID } from 'crypto';
import { Column, Entity, ManyToOne, Relation } from 'typeorm';

@Entity('courses')
export class CourseSchema extends BaseSchema {
Expand All @@ -13,6 +14,9 @@ export class CourseSchema extends BaseSchema {
@Column({ type: 'decimal' })
price: number;

@Column()
instructorId: UUID;

@ManyToOne(() => UserSchema, { eager: true })
instructor: UserSchema;
instructor?: Relation<UserSchema>;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { CourseEntity } from '@modules/course/domain/entities/course/course.entity';
import { UserViewModel } from '@modules/user/presenter/models/view-models/user.view-model';
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { BaseEntityViewModel } from '@shared/presenter/models/base-entity.view-model';
import { UUID } from 'crypto';

export class CourseViewModel extends BaseEntityViewModel {
constructor(entity: CourseEntity) {
Expand All @@ -10,7 +11,10 @@ export class CourseViewModel extends BaseEntityViewModel {
this.title = entity.title;
this.description = entity.description;
this.price = entity.price.amount;
this.instructor = new UserViewModel(entity.instructor);
this.instructorId = entity.instructorId;

if (entity.instructor)
this.instructor = new UserViewModel(entity.instructor);
}

@ApiProperty({ example: 'Flutter with Clean Architecture and TDD' })
Expand All @@ -25,6 +29,9 @@ export class CourseViewModel extends BaseEntityViewModel {
@ApiProperty({ example: 49.99 })
price: number;

@ApiProperty({ type: () => UserViewModel })
instructor: UserViewModel;
@ApiProperty()
instructorId: UUID;

@ApiPropertyOptional({ type: () => UserViewModel })
instructor?: UserViewModel;
}

0 comments on commit 136b718

Please sign in to comment.