Skip to content

Commit

Permalink
Feat(Course, Instructor, Route): Get course instructor details route
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur-Poffo committed Feb 23, 2024
1 parent 67a2bf7 commit 8ac5ec1
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
- [x] GET /students/:studentId/enrollments - Get student courses with instructor and evaluations

### Instructors
- [ ] GET /courses/:courseId/instructor - Get course instructor details
- [x] GET /courses/:courseId/instructors - Get course instructor details
- [ ] GET /instructors/:instructorId/courses - Get instructor courses with instructor and evaluations

### Courses
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error'
import { makeCourse } from '../../../../../test/factories/make-course'
import { makeInstructor } from '../../../../../test/factories/make-instructor'
import { InMemoryClassesRepository } from '../../../../../test/repositories/in-memory-classes-repository'
import { InMemoryCourseTagsRepository } from '../../../../../test/repositories/in-memory-course-tags-repository'
import { InMemoryCoursesRepository } from '../../../../../test/repositories/in-memory-courses-repository'
import { InMemoryEnrollmentCompletedItemsRepository } from '../../../../../test/repositories/in-memory-enrollment-completed-items-repository'
import { InMemoryEnrollmentsRepository } from '../../../../../test/repositories/in-memory-enrollments-repository'
import { InMemoryInstructorRepository } from '../../../../../test/repositories/in-memory-instructors-repository'
import { InMemoryModulesRepository } from '../../../../../test/repositories/in-memory-modules-repository'
import { InMemoryStudentsRepository } from '../../../../../test/repositories/in-memory-students-repository'
import { GetCourseInstructorDetailsUseCase } from './get-course-instructor-details'

let inMemoryEnrollmentCompletedItemsRepository: InMemoryEnrollmentCompletedItemsRepository
let inMemoryClassesRepository: InMemoryClassesRepository
let inMemoryCourseTagsRepository: InMemoryCourseTagsRepository
let inMemoryEnrollmentsRepository: InMemoryEnrollmentsRepository
let inMemoryStudentsRepository: InMemoryStudentsRepository
let inMemoryInstructorsRepository: InMemoryInstructorRepository
let inMemoryModulesRepository: InMemoryModulesRepository
let inMemoryCoursesRepository: InMemoryCoursesRepository
let sut: GetCourseInstructorDetailsUseCase

describe('Get course instructor details use case', () => {
beforeEach(() => {
inMemoryEnrollmentCompletedItemsRepository = new InMemoryEnrollmentCompletedItemsRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryCourseTagsRepository = new InMemoryCourseTagsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository(
inMemoryStudentsRepository, inMemoryEnrollmentCompletedItemsRepository
)
inMemoryCoursesRepository = new InMemoryCoursesRepository(inMemoryModulesRepository, inMemoryInstructorsRepository, inMemoryEnrollmentsRepository, inMemoryStudentsRepository, inMemoryCourseTagsRepository)

sut = new GetCourseInstructorDetailsUseCase(inMemoryCoursesRepository, inMemoryInstructorsRepository)
})

it('should be able to get course instructor details', async () => {
const instructor = makeInstructor({ name: 'John Doe' })
await inMemoryInstructorsRepository.create(instructor)

const course = makeCourse({ name: 'John Doe Course', instructorId: instructor.id })
await inMemoryCoursesRepository.create(course)

const result = await sut.exec({
courseId: course.id.toString()
})

expect(result.isRight()).toBe(true)
expect(result.value).toMatchObject({
instructor: expect.objectContaining({
name: 'John Doe'
})
})
})

it('should not be able to get course details of a inexistent course', async () => {
const result = await sut.exec({
courseId: 'inexistentCourseId'
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(ResourceNotFoundError)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { left, right, type Either } from '@/core/either'
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error'
import { type UseCase } from '@/core/use-cases/use-case'
import { type Instructor } from '../../enterprise/entities/instructor'
import { type CoursesRepository } from '../repositories/courses-repository'
import { type InstructorsRepository } from '../repositories/instructors-repository'

interface GetCourseInstructorDetailsUseCaseRequest {
courseId: string
}

type GetCourseInstructorDetailsUseCaseResponse = Either<
ResourceNotFoundError,
{
instructor: Instructor
}
>

export class GetCourseInstructorDetailsUseCase implements UseCase<GetCourseInstructorDetailsUseCaseRequest, GetCourseInstructorDetailsUseCaseResponse> {
constructor(
private readonly coursesRepository: CoursesRepository,
private readonly instructorsRepository: InstructorsRepository
) { }

async exec({
courseId
}: GetCourseInstructorDetailsUseCaseRequest): Promise<GetCourseInstructorDetailsUseCaseResponse> {
const course = await this.coursesRepository.findById(courseId)

if (!course) {
return left(new ResourceNotFoundError())
}

const courseInstructor = await this.instructorsRepository.findById(course.instructorId.toString())

if (!courseInstructor) {
return left(new ResourceNotFoundError())
}

return right({
instructor: courseInstructor
})
}
}
38 changes: 38 additions & 0 deletions src/infra/http/controllers/get-course-instructor-details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error'
import { makeInstructorMapper } from '@/infra/database/prisma/mappers/factories/make-instructor-mapper'
import { makeGetCourseInstructorDetailsUseCase } from '@/infra/use-cases/factories/make-get-course-instructor-details-use-case'
import { type FastifyReply, type FastifyRequest } from 'fastify'
import { z } from 'zod'
import { UserPresenter } from '../presenters/user-presenter'

const getCourseInstructorDetailsParamsSchema = z.object({
courseId: z.string().uuid()
})

export async function getCourseInstructorDetailsController(request: FastifyRequest, reply: FastifyReply) {
const { courseId } = getCourseInstructorDetailsParamsSchema.parse(request.params)

const getCourseInstructorDetailsUseCase = makeGetCourseInstructorDetailsUseCase()

const result = await getCourseInstructorDetailsUseCase.exec({
courseId
})

if (result.isLeft()) {
const error = result.value

switch (error.constructor) {
case ResourceNotFoundError:
return await reply.status(404).send({ message: error.message })
default:
return await reply.status(500).send({ message: error.message })
}
}

const instructorMapper = makeInstructorMapper()
const instructor = await instructorMapper.toPrisma(result.value.instructor)

return await reply.status(200).send({
instructor: UserPresenter.toHTTP(instructor)
})
}
2 changes: 2 additions & 0 deletions src/infra/http/routes/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { editCourseDetailsController } from '../controllers/edit-course-details'
import { fetchCourseStudentsController } from '../controllers/fetch-course-students'
import { fetchRecentCoursesController } from '../controllers/fetch-recent-courses'
import { getCourseDetailsController } from '../controllers/get-course-details'
import { getCourseInstructorDetailsController } from '../controllers/get-course-instructor-details'
import { getCourseMetricsController } from '../controllers/get-course-metrics'
import { getCourseStatsController } from '../controllers/get-course-stats'
import { queryCoursesByNameController } from '../controllers/query-courses-by-name'
Expand All @@ -19,6 +20,7 @@ export async function courseRoutes(app: FastifyInstance) {
app.get('/courses/filter/tags', queryCoursesByTagController)
app.get('/courses/:courseId/stats', getCourseStatsController)
app.get('/courses/:courseId/students', fetchCourseStudentsController)
app.get('/courses/:courseId/instructors', getCourseInstructorDetailsController)

app.get('/courses/:courseId/metrics', { onRequest: [verifyJwt, verifyUserRole('INSTRUCTOR')] }, getCourseMetricsController)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { GetCourseInstructorDetailsUseCase } from '@/domain/course-management/application/use-cases/get-course-instructor-details'
import { makePrismaCoursesRepository } from '@/infra/database/prisma/repositories/factories/make-prisma-courses-repository'
import { makePrismaInstructorsRepository } from '@/infra/database/prisma/repositories/factories/make-prisma-instructors-repository'

export function makeGetCourseInstructorDetailsUseCase() {
const prismaCoursesRepository = makePrismaCoursesRepository()
const prismaInstructorsRepository = makePrismaInstructorsRepository()

const getCourseDetailsUseCase = new GetCourseInstructorDetailsUseCase(
prismaCoursesRepository,
prismaInstructorsRepository
)

return getCourseDetailsUseCase
}

0 comments on commit 8ac5ec1

Please sign in to comment.