Skip to content

Commit

Permalink
Merge pull request #21 from Artur-Poffo/feat-mark-class-and-module-as…
Browse files Browse the repository at this point in the history
…-completed

Feat(Module, Class, Enrollment): Mark classes and modules as completed
  • Loading branch information
Artur-Poffo committed Feb 8, 2024
2 parents f66c437 + 436ce47 commit 99d5ef6
Show file tree
Hide file tree
Showing 26 changed files with 609 additions and 24 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
- [x] Students can "enroll" to participate in the course.
- [x] Return information about a course with its students.
- [ ] Return information about a course with student progress in your modules and classes.
- [ ] Students can mark classes as completed.
- [ ] Return information about a student with the courses they are enrolled in.
- [ ] Mark modules as completed after the student views all its classes.
- [x] Students can mark classes as completed.
- [x] Mark modules as completed after the student views all its classes.
- [ ] After completing all modules of a course, that course for a student should be marked as completed.
- [ ] Return information about a student with the courses they are enrolled in.
- [ ] After completing a course, the student can issue a certificate.

- [ ] It should be possible to filter courses by name or "tags."
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "code-spark-api",
"name": "@codespark/server",
"version": "1.0.0",
"description": "Entra21 course final paper",
"main": "./dist/infra/server.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ export interface EnrollmentsRepository {
findById: (id: string) => Promise<Enrollment | null>
findByStudentIdAndCourseId: (studentId: string, courseId: string) => Promise<Enrollment | null>
findManyByCourseId: (courseId: string) => Promise<Enrollment[]>
markClassAsCompleted: (classId: string, enrollment: Enrollment) => Promise<Enrollment | null>
markModuleAsCompleted: (moduleId: string, enrollment: Enrollment) => Promise<Enrollment | null>
create: (enrollment: Enrollment) => Promise<Enrollment>
save: (classToSave: Enrollment) => Promise<void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type ModuleWithClassesDTO } from './../../enterprise/entities/dtos/modu
export interface ModulesRepository {
findById: (id: string) => Promise<Module | null>
findManyByCourseId: (id: string) => Promise<Module[]>
findManyClassesByCourseId: (courseId: string) => Promise<Class[] | null>
findManyClassesByCourseId: (courseId: string) => Promise<Class[]>
findModuleWithClassesById: (id: string) => Promise<ModuleWithClassesDTO | null>
create: (module: Module) => Promise<Module>
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ describe('Add class to a module use case', () => {
beforeEach(() => {
inMemoryVideosRepository = new InMemoryVideosRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository(
inMemoryClassesRepository, inMemoryModulesRepository
)
inMemoryCoursesRepository = new InMemoryCoursesRepository(
inMemoryModulesRepository, inMemoryInstructorsRepository, inMemoryEnrollmentsRepository, inMemoryStudentsRepository
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,7 @@ export class AddClassToModuleUseCase implements UseCase<AddClassToModuleUseCaseR
return left(new NotAllowedError())
}

const courseClasses: Class[] = []

completeCourse.modules.forEach(moduleToExtract => {
courseClasses.push(...moduleToExtract.classes)
})
const courseClasses: Class[] = await this.modulesRepository.findManyClassesByCourseId(completeCourse.course.id.toString())

const classesInThisModule = courseClasses.filter(classToCompare => classToCompare.moduleId.toString() === moduleId)
const classWithSameNameInSameModule = classesInThisModule.find(classToCompare => classToCompare.name === name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ describe('Attach tag to course use case', () => {
inMemoryCourseTagsRepository = new InMemoryCourseTagsRepository()
inMemoryTagsRepository = new InMemoryTagsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new AttachTagToCourseUseCase(inMemoryCourseTagsRepository, inMemoryTagsRepository, inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ let sut: EnrollToCourseUseCase

describe('Enroll to course use case', () => {
beforeEach(() => {
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new EnrollToCourseUseCase(inMemoryEnrollmentsRepository, inMemoryStudentsRepository, inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-e
import { type UseCase } from '@/core/use-cases/use-case'
import { Enrollment } from '../../enterprise/entities/enrollment'
import { type CoursesRepository } from '../repositories/courses-repository'
import { type EnrollmentsRepository } from '../repositories/enrollments'
import { type EnrollmentsRepository } from '../repositories/enrollments-repository'
import { type StudentsRepository } from '../repositories/students-repository'
import { AlreadyEnrolledInThisCourse } from './errors/already-enrolled-in-this-course'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type UseCaseError } from '@/core/errors/use-case-error'

export class AllClassesInTheModuleMustBeMarkedAsCompleted extends Error implements UseCaseError {
constructor(moduleName: string) {
super(`All classes in the module: ${moduleName}, must be marked as completed`)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ let sut: GetCourseDetailsUseCase
describe('Get course details use case', () => {
beforeEach(() => {
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new GetCourseDetailsUseCase(inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ describe('Get course details with modules and classes use case', () => {
beforeEach(() => {
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new GetCourseWithModulesAndClassesUseCase(inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ describe('Get course details with modules use case', () => {
beforeEach(() => {
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new GetCourseWithModulesUseCase(inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ let sut: GetCourseWithStudentsUseCase

describe('Get course details with students use case', () => {
beforeEach(() => {
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new GetCourseWithStudentsUseCase(inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ describe('Get instructors with their courses', () => {
beforeEach(() => {
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new GetInstructorWithCoursesUseCase(inMemoryCoursesRepository)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { NotAllowedError } from '@/core/errors/errors/not-allowed-error'
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error'
import { makeClass } from '../../../../../test/factories/make-class'
import { makeCourse } from '../../../../../test/factories/make-course'
import { makeEnrollment } from '../../../../../test/factories/make-enrollment'
import { makeInstructor } from '../../../../../test/factories/make-instructor'
import { makeModule } from '../../../../../test/factories/make-module'
import { makeStudent } from '../../../../../test/factories/make-student'
import { InMemoryClassesRepository } from '../../../../../test/repositories/in-memory-classes-repository'
import { InMemoryCoursesRepository } from '../../../../../test/repositories/in-memory-courses-repository'
import { InMemoryEnrollmentsRepository } from '../../../../../test/repositories/in-memory-enrollments-repository'
import { InMemoryStudentsRepository } from '../../../../../test/repositories/in-memory-students-repository'
import { InMemoryInstructorRepository } from './../../../../../test/repositories/in-memory-instructors-repository'
import { InMemoryModulesRepository } from './../../../../../test/repositories/in-memory-modules-repository'
import { MarkClassAsCompletedUseCase } from './mark-class-as-completed'

let inMemoryEnrollmentsRepository: InMemoryEnrollmentsRepository
let inMemoryStudentsRepository: InMemoryStudentsRepository
let inMemoryClassesRepository: InMemoryClassesRepository
let inMemoryInstructorsRepository: InMemoryInstructorRepository
let inMemoryModulesRepository: InMemoryModulesRepository
let inMemoryCoursesRepository: InMemoryCoursesRepository
let sut: MarkClassAsCompletedUseCase

describe('Mark class as completed use case', () => {
beforeEach(() => {
inMemoryStudentsRepository = new InMemoryStudentsRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

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

sut = new MarkClassAsCompletedUseCase(
inMemoryEnrollmentsRepository, inMemoryCoursesRepository, inMemoryModulesRepository, inMemoryClassesRepository, inMemoryStudentsRepository
)
})

it('should be able to mark a class of a enrollment as completed', async () => {
const instructor = makeInstructor()
await inMemoryInstructorsRepository.create(instructor)

const course = makeCourse({ instructorId: instructor.id })
await inMemoryCoursesRepository.create(course)

const module = makeModule({
courseId: course.id,
moduleNumber: 1
})
await inMemoryModulesRepository.create(module)

const classToMarkAsCompleted = makeClass({ name: 'John Doe Class', moduleId: module.id, classNumber: 1 })
await inMemoryClassesRepository.create(classToMarkAsCompleted)

const student = makeStudent()
await inMemoryStudentsRepository.create(student)

const enrollment = makeEnrollment({ studentId: student.id, courseId: course.id })
await inMemoryEnrollmentsRepository.create(enrollment)

const result = await sut.exec({
enrollmentId: enrollment.id.toString(),
classId: classToMarkAsCompleted.id.toString(),
studentId: student.id.toString()
})

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

it('should not be able to mark a inexistent class as completed', async () => {
const instructor = makeInstructor()
await inMemoryInstructorsRepository.create(instructor)

const course = makeCourse({ instructorId: instructor.id })
await inMemoryCoursesRepository.create(course)

const module = makeModule({
courseId: course.id,
moduleNumber: 1
})
await inMemoryModulesRepository.create(module)

const student = makeStudent()
await inMemoryStudentsRepository.create(student)

const enrollment = makeEnrollment({ studentId: student.id, courseId: course.id })
await inMemoryEnrollmentsRepository.create(enrollment)

const result = await sut.exec({
enrollmentId: enrollment.id.toString(),
classId: 'inexistentClassId',
studentId: student.id.toString()
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(ResourceNotFoundError)
})

it('should not be able to mark a class of a enrollment as completed if the student not is the owner of the enrollment', async () => {
const instructor = makeInstructor()
await inMemoryInstructorsRepository.create(instructor)

const course = makeCourse({ instructorId: instructor.id })
await inMemoryCoursesRepository.create(course)

const module = makeModule({
courseId: course.id,
moduleNumber: 1
})
await inMemoryModulesRepository.create(module)

const classToMarkAsCompleted = makeClass({ name: 'John Doe Class', moduleId: module.id, classNumber: 1 })
await inMemoryClassesRepository.create(classToMarkAsCompleted)

const correctStudent = makeStudent()
const wrongStudent = makeStudent()

await Promise.all([
inMemoryStudentsRepository.create(correctStudent),
inMemoryStudentsRepository.create(wrongStudent)
])

const enrollment = makeEnrollment({ studentId: correctStudent.id, courseId: course.id })
await inMemoryEnrollmentsRepository.create(enrollment)

const result = await sut.exec({
enrollmentId: enrollment.id.toString(),
classId: classToMarkAsCompleted.id.toString(),
studentId: wrongStudent.id.toString()
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(NotAllowedError)
})
})
Loading

0 comments on commit 99d5ef6

Please sign in to comment.