Skip to content

Commit

Permalink
Feat(Course, Instructor, Route): Fetch instructor courses route
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur-Poffo committed Feb 23, 2024
1 parent 8ac5ec1 commit b16b2f3
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@

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

### Courses
- [x] GET /courses/:courseId - Get course details
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { OnFileUploaded } from '@/domain/course-management/application/subscribers/on-file-uploaded'
import { GetVideoDuration } from '@/infra/storage/utils/get-video-duration'
import { InMemoryVideosRepository } from '../../../../../test/repositories/in-memory-videos-repository'
import { waitFor } from '../../../../../test/utils/wait-for'
import { InMemoryFilesRepository } from './../../../../../test/repositories/in-memory-files-repository'
Expand All @@ -10,6 +11,7 @@ let inMemoryFilesRepository: InMemoryFilesRepository
let inMemoryImagesRepository: InMemoryImagesRepository
let inMemoryVideosRepository: InMemoryVideosRepository
let fakeUploader: FakeUploader
let getVideoDuration: GetVideoDuration
let uploadFileUseCase: UploadFileUseCase

describe('On file uploaded event', () => {
Expand All @@ -18,14 +20,16 @@ describe('On file uploaded event', () => {
inMemoryImagesRepository = new InMemoryImagesRepository()
inMemoryVideosRepository = new InMemoryVideosRepository()
fakeUploader = new FakeUploader()
getVideoDuration = new GetVideoDuration()
uploadFileUseCase = new UploadFileUseCase(
inMemoryFilesRepository,
fakeUploader
)

new OnFileUploaded(
inMemoryImagesRepository,
inMemoryVideosRepository
inMemoryVideosRepository,
getVideoDuration
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Image } from '../../enterprise/entities/image'
import { Video } from '../../enterprise/entities/video'
import { type ImagesRepository } from '../repositories/images-repository'
import { type VideosRepository } from '../repositories/videos-repository'
import { type GetVideoDuration } from './../../../../infra/storage/utils/get-video-duration'

export class OnFileUploaded implements EventHandler {
constructor(
private readonly imagesRepository: ImagesRepository,
private readonly videosRepository: VideosRepository
private readonly videosRepository: VideosRepository,
private readonly getVideoDuration: GetVideoDuration
) {
this.setupSubscriptions()
}
Expand All @@ -34,13 +36,15 @@ export class OnFileUploaded implements EventHandler {

await this.imagesRepository.create(image)
} else if (/video\/(mp4|avi)/.test(file.fileType)) {
const duration = await this.getVideoDuration.getInSecondsByBuffer(file.body)

const video = Video.create({
body: file.body,
videoName: file.fileName,
size: file.size,
videoKey: file.fileKey,
videoType: file.fileType as 'video/mp4' | 'video/avi',
duration: 24,
duration: Math.round(duration),
storedAt: file.storedAt
})

Expand Down
3 changes: 2 additions & 1 deletion src/infra/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { enrollmentRoutes } from './http/routes/enrollment'
import { evaluationRoutes } from './http/routes/evaluation'
import { fileRoutes } from './http/routes/file'
import { imageRoutes } from './http/routes/image'
import { instructorRoutes } from './http/routes/instructor'
import { moduleRoutes } from './http/routes/module'
import { studentRoutes } from './http/routes/student'
import { studentCertificateRoutes } from './http/routes/student-certificate'
Expand Down Expand Up @@ -48,13 +49,13 @@ app.register(fastifyJwt, {
app.register(fastifyCookie)

export const upload = multer()

app.register(multer.contentParser)

// API Routes

app.register(userRoutes)
app.register(studentRoutes)
app.register(instructorRoutes)
app.register(courseRoutes)
app.register(fileRoutes)
app.register(imageRoutes)
Expand Down
5 changes: 4 additions & 1 deletion src/infra/events/factories/make-on-file-uploaded.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { OnFileUploaded } from '@/domain/course-management/application/subscribers/on-file-uploaded'
import { PrismaImagesRepository } from '@/infra/database/prisma/repositories/prisma-images-repository'
import { PrismaVideosRepository } from '@/infra/database/prisma/repositories/prisma-videos-repository'
import { GetVideoDuration } from './../../storage/utils/get-video-duration'

export function makeOnFileUploaded() {
const prismaImagesRepository = new PrismaImagesRepository()
const prismaVideosRepository = new PrismaVideosRepository()
const getVideoDuration = new GetVideoDuration()

const onVideoKeyGenerated = new OnFileUploaded(
prismaImagesRepository,
prismaVideosRepository
prismaVideosRepository,
getVideoDuration
)

return onVideoKeyGenerated
Expand Down
93 changes: 93 additions & 0 deletions src/infra/http/controllers/fetch-instructor-courses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error'
import { type CourseWithInstructorAndEvaluationDTO } from '@/domain/course-management/enterprise/entities/dtos/course-with-instructor-and-evaluation'
import { CourseDtoMapper } from '@/domain/course-management/enterprise/entities/dtos/mappers/course-dto-mapper'
import { makeGetCourseEvaluationsAverageUseCase } from '@/infra/use-cases/factories/make-get-course-evaluations-average-use-case'
import { makeGetInstructorWithCoursesUseCase } from '@/infra/use-cases/factories/make-get-instructor-with-courses-use-case'
import { makeGetUserInfoUseCase } from '@/infra/use-cases/factories/make-get-user-info-use-case'
import { type FastifyReply, type FastifyRequest } from 'fastify'
import { z } from 'zod'
import { CoursesWithInstructorAndEvaluationPresenter } from '../presenters/courses-with-instructor-and-evaluation-presenter'

const fetchInstructorCoursesParamsSchema = z.object({
instructorId: z.string().uuid()
})

export async function fetchInstructorCoursesController(request: FastifyRequest, reply: FastifyReply) {
const { instructorId } = fetchInstructorCoursesParamsSchema.parse(request.params)

const fetchInstructorCoursesUseCase = makeGetInstructorWithCoursesUseCase()
const getUserInfoUseCase = makeGetUserInfoUseCase()
const getCourseEvaluationsAverageUseCase = makeGetCourseEvaluationsAverageUseCase()

const result = await fetchInstructorCoursesUseCase.exec({
instructorId
})

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 courses = result.value.instructorWithCourses.courses

const coursesWithInstructorAndEvaluation: CourseWithInstructorAndEvaluationDTO[] = []

await Promise.all(
courses.map(async (course) => {
const instructorResult = await getUserInfoUseCase.exec({
id: course.instructorId.toString()
})
const courseEvaluationAverageResult = await getCourseEvaluationsAverageUseCase.exec({
courseId: course.id.toString()
})

if (instructorResult.isLeft()) {
const error = instructorResult.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 })
}
}

if (courseEvaluationAverageResult.isLeft()) {
return await reply.status(500).send()
}

const { user } = instructorResult.value
const { evaluationsAverage } = courseEvaluationAverageResult.value

const courseWithInstructorAndEvaluation: CourseWithInstructorAndEvaluationDTO = {
course: CourseDtoMapper.toDTO(course),
instructor: {
id: user.id,
name: user.name,
email: user.email,
age: user.age,
registeredAt: user.registeredAt,
summary: user.summary,
bannerImageKey: user.bannerImageKey,
profileImageKey: user.profileImageKey
},
evaluationsAverage
}

coursesWithInstructorAndEvaluation.push(courseWithInstructorAndEvaluation)
})
)

return await reply.status(200).send({
courses: coursesWithInstructorAndEvaluation.map(courseWithInstructorAndEvaluation =>
CoursesWithInstructorAndEvaluationPresenter.toHTTP(courseWithInstructorAndEvaluation
)
)
})
}
6 changes: 6 additions & 0 deletions src/infra/http/routes/instructor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type FastifyInstance } from 'fastify'
import { fetchInstructorCoursesController } from '../controllers/fetch-instructor-courses'

export async function instructorRoutes(app: FastifyInstance) {
app.get('/instructors/:instructorId/courses', fetchInstructorCoursesController)
}
3 changes: 1 addition & 2 deletions src/infra/http/routes/student.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type FastifyInstance } from 'fastify'
import { fetchStudentCoursesController } from '../controllers/fetch-student-courses'
import { verifyJwt } from '../middlewares/verify-jwt'

export async function studentRoutes(app: FastifyInstance) {
app.get('/students/:studentId/enrollments', { onRequest: [verifyJwt] }, fetchStudentCoursesController)
app.get('/students/:studentId/enrollments', fetchStudentCoursesController)
}

0 comments on commit b16b2f3

Please sign in to comment.