boolean-uk / team-dev-server

3 stars 12 forks source link

#76 As a teacher, so I can plan my curriculum, I want to organise exercises by modules and units #299

Open mnmin opened 1 year ago

mnmin commented 1 year ago

User Story

A lesson should have a day number (not a date, more like a number to represent where it is in the delivery sequence). This can't be an ID as lessons might be re-ordered

Teachers should be able to see a high-level view of all modules. Clicking a module should show its data and a list of its units. Clicking a unit should show its data and a list of its lessons. Clicking a lesson should show its data and a list of its exercises.

Tasks

SERVER SIDE

Check List

Overview

  1. Create Prisma Schema
  2. Create methods in the seed file
  3. Create modules for routes and controllers with methods
  4. Add event emitters to all methods

1 - Create Prisma Schema

Use Prisma Schema Documentation to create: Curriculum, Module, Unit, Lessons, Lesson Plan: https://www.prisma.io/docs/concepts/components/prisma-client/crud

Prisma Schema For Curriculum

2 - Create methods in the seed file

Overview In Seed.js

  1. Create a curriculum
  2. Create two modules
  3. Create two units for each module
  4. Create two lessons for each unit
  5. Create one lesson plan for each lesson

1 - Create a curriculum

  const newCurriculum = await prisma.curriculum.create({
    data: {
      content: ''
    }
  })

2 - Create n seeds for each component

const iterator = 2
const iterator = 2

  const curriculums = []
  const createCurriculum = async () => {
    const newCurriculum = await prisma.curriculum.create({
      data: {
        name: 'Javascript',
        description:
          'Learn the JavaScript fundamentals you will need for front-end or back-end development'
      }
    })
    curriculums.push(newCurriculum)
  }

for (let i = 1; i <= iterator; i++) {
    await createCurriculum()
  }

console.log(
    'Curriculums, curriculums)

3 - Sample API response

curriculum, module, unit, lesson, exercise, lessonPlan {
  id: 1,
  name: 'Javascript',
  description: 'Learn the JavaScript fundamentals you will need for front-end or back-end development',
  createdAt: 2022-10-05T12:09:21.370Z,
  updatedAt: 2022-10-05T12:09:21.370Z
} {
  id: 1,
  name: 'Module 1',
  description: 'Description for Module-1',
  objectives: [
    'Objective 1 for module-1 Curriculum-1',
    'Objective 2 for module-1 Curriculum-1',
    'Objective 2 for module-1 Curriculum-1',
    'Objective 2 for module-1 Curriculum-1',
    'Objective 2 for module-1 Curriculum-1'
  ],
  createdAt: 2022-10-05T12:09:21.716Z,
  updatedAt: 2022-10-05T12:09:21.717Z
} {
  id: 1,
  name: 'Unit 1',
  description: 'Description for Unit-1',
  objectives: [
    'Objective 1 for Unit-1 Module-1 Curriculum-1',
    'Objective 1 for Unit-1 Module-1 Curriculum-1',
    'Objective 1 for Unit-1 Module-1 Curriculum-1',
    'Objective 1 for Unit-1 Module-1 Curriculum-1',
    'Objective 1 for Unit-1 Module-1 Curriculum-1'
  ],
  moduleId: 1,
  createdAt: 2022-10-05T12:09:22.284Z,
  updatedAt: 2022-10-05T12:09:22.285Z
} {
  id: 1,
  dayNumber: 1,
  name: 'Lesson 1',
  description: 'Lesson description for 1',
  objectives: [
    'Objective 1 for lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 2 for lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 3 for lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 4 for lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 5 for lesson-1 Unit-1 Module-1 Curriculum-1'
  ],
  unitId: 1,
  createdAt: 2022-10-05T12:09:22.632Z,
  updatedAt: 2022-10-05T12:09:22.632Z
} {
  id: 3,
  name: 'Exercise 1',
  gitHubUrl: 'https://github.com/boolean-uk/html-scientific-paper',
  readMeUrl: 'https://raw.githubusercontent.com/boolean-uk/html-scientific-paper/main/README.md',  
  objectives: [
    'Objective 1 for exercise 1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 2 for exercise 1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 3 for exercise 1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 4 for exercise 1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 5 for exercise 1 lesson-1 Unit-1 Module-1 Curriculum-1'
  ],
  createdAt: 2022-10-05T12:09:22.972Z,
  updatedAt: 2022-10-05T12:09:22.973Z
} {
  id: 1,
  name: 'Lesson Plan 1',
  description: 'Lesson plan description for 1',
  objectives: [
    'Objective 1 for  lesson plan-1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 2 for lesson plan-1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 3 for lesson plan-1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 4 for lesson plan-1 lesson-1 Unit-1 Module-1 Curriculum-1',
    'Objective 5 for lesson plan-1 lesson-1 Unit-1 Module-1 Curriculum-1'
  ],
  lessonId: 1,
  createdById: 1,
  createdForId: 4,
  createdAt: 2022-10-05T12:09:23.539Z,
  updatedAt: 2022-10-05T12:09:23.540Z
}

3 - Create modules for routes and controllers with methods

  1. Create a new route for curriculum in index.js
    app.use(‘/curriculum’, curriculumRouter)
  2. In routes folder create curriculum.js file and import this file in index.js. Add the following routers

Routers --> routers folder --> curriculum.js CURRICULUM - routers

  1. post --> router.post(‘/’, validateAuthentication, createCurriculum)
  2. get --> router.get(‘/’, validateAuthentication, getAllCurriculums)
  3. put --> router.put(‘/:id’, validateAuthentication, updateCurriculumById)
  4. delete --> router.delete(‘/:id’, validateAuthentication, deleteCurriculumById)

MODULE - routers

  1. post --> router.post(‘/:id/module’, validateAuthentication, createModule)
  2. get --> router.get(‘/:id/module’, validateAuthentication, getAllModules)
  3. get --> router.get('/:id/module/:moduleId', validateAuthentication, getModuleById)
  4. put --> router.put(‘/:id/module/:moduleId’, validateAuthentication, updateModuleById)
  5. delete --> router.delete(‘/:id/module/:moduleId’, validateAuthentication, deleteModuleById)

UNIT - routers

  1. post --> router.post(‘/:id/module/:moduleId/unit’, validateAuthentication, createUnit)
  2. get --> router.get(‘/:id/module/:moduleId/unit, validateAuthentication, getAllUnits)
  3. get --> router.get(‘/:id/module/:moduleId/unit/:unitId’, validateAuthentication, getUnitById)
  4. put --> router.put(‘/:id/module/:moduleId/unit/:unitId’, validateAuthentication, updateUnitById)
  5. delete --> router.delete(‘/:id/module/:moduleId/unit/:unitId’, validateAuthentication, deleteUnitById)

LESSON - routers

  1. post --> router.post(‘/:id/module/:moduleId/unit/:unitId/lesson’, validateAuthentication, createLesson)
  2. get --> router.get(‘/:id/module/:moduleId/unit/:unitId/lesson', validateAuthentication, getAllLessons)
  3. get --> router.get(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId’, validateAuthentication, getLessonById)
  4. put --> router.put(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId’, validateAuthentication, updateLessonById)
  5. delete --> router.delete(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId’, validateAuthentication, deleteLessonById)

LESSON PLAN - routers

  1. post --> router.post(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId/lessonPlan’, validateAuthentication, createLessonPlan)
  2. get --> router.get(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId/lessonPlan, validateAuthentication, getLessonPlan)
  3. put --> router.put(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId/lessonPlan’, validateAuthentication, updateLessonPlan)
  4. delete --> router.delete(‘/:id/module/:moduleId/unit/:unitId/lesson/:lessonId/lessonPlan’, validateAuthentication, deleteLessonPlan)

CREATE METHODS FOR CONTROLLER Create the following methods in controllers folder --> curriculum.js

Curriculum createCurriculum() getAllCurriculums() updateCurriculumById() deleteCurriculumById()

Module createModule() getAllModules() getModuleById() updateModuleById() deleteModuleById()

Unit createUnit() getAllUnits() getUnitById() updateUnitById() deleteUnitById()

Lesson createLesson() getAllLessons() getLessonById() updateLessonById() deleteLessonById()

LessonPlan createLessonPlan() getLessonPlan() updateLessonPlan() deleteLessonPlan()

Sample API response structures:

CREATE
return sendDataResponse(res, 201, module)
return sendMessageResponse(res, 400, 'Must provide content')
GET ALL
```js
return sendDataResponse(res, 200, module)
GET BY ID
return sendDataResponse(res, 200, { module: foundModule })
return sendMessageResponse(res, 404, 'module not found')

UPDATE BY ID

4 - Add event emitters to all methods

Overview In eventEmitter --> utils.js Add event emitters only for important user actions

  1. Add 3 methods for Curriculum --> create, delete, update
  2. Add 3 methods for module --> create, delete, update
  3. Add 3 methods for unit --> create, delete, update
  4. Add 3 methods for lesson --> create, delete, update
  5. Add 3 methods for lessonPlan --> create, delete, update
    • add methods if necessary

-Example only for batabase error createModuleCreatedEvent

export const createModuleCreatedEvent = async (module, user) => {
  try {
    await dbClient.event.create({
      data: {
        type: 'MODULE',
        topic: 'create',
        createdById: user.id,
        moduleId: module.id,
        createdAt: module.createdAt
      }
    })
  } catch (err) {
    const error = new CreateEventError(user, 'create-module')
    myEmitter.emit('error', error)
    throw err
  }
}

Use classes in eventEmitter folder --> utils.js file to record important events.

In eventEmitter --> index.js

Add event listeners for each important user action, follow the existing naming convention for events eg:

myEmitter.on('create-module', (module, user) =>
  createModuleCreatedEvent(module, user)
)
vherus commented 1 year ago

This is an excellent start. Here are some notes:

Since you'll be adding some many-to-many relationships to satisfy the above, we'll need some sample API response structures so the people working on the client side can get started and build to that spec before the backend is complete

ning905 commented 1 year ago

This is a very detailed plan! Looks great! Nice job!! One thing about the event emitter is that the class CreateEventError is only used for catching the error when the database fails to create an event to record something it should create an event for. So you might want to use other error classes in that file, or create new classes if needed.

mnmin commented 1 year ago

Ok, making changes to the plan

mnmin commented 1 year ago

Plan Prisma schema and added sample API response structures

Auenc commented 1 year ago

This all looks great, excellent work!

I do just have a question about this snippet:

const iterator = 2
for (let mod = 0; mod < iterator; mod++) {
  const module = await prisma.module.create({})
  for (let un = 0; un < iterator; un++) {
    const unit = await prisma.unit.create({})
    for (let les = 0; les < iterator; les++) {
      const lesson = await prisma.lesson.create({})
      const lessonPlan = await prisma.lessonPlan.create({})
      console.log('module, unit, lesson, lessonPlan', module, unit, lesson, lessonPlan)
    }
  }
}

What is the purpose of this? Is it to seed the database?

mnmin commented 1 year ago

yes, it seeds the database. I made some changes (adding exercises to the for loop).

Auenc commented 1 year ago

I think it would be better if instead you split out creating each of the entities into their own function. For instance,

const createUnit = async (module) => {
  return prisma.unit.create({connect: {id: module.id})
}

That way, you'll be able to seed all of the data that you wish by passing in values via the parameters, and it'll be a lot easier to maintain the seed file in the future (reading 3 nested loops can be quite confusing)

mnmin commented 1 year ago
const iterator = 2
  for (let curr = 1; curr <= iterator; curr++) {
    const curriculum = await prisma.curriculum.create({
      data: {
        name: 'Javascript',
        description:
          'Learn the JavaScript fundamentals you will need for front-end or back-end development'
      }
    })
    for (let mod = 1; mod <= iterator; mod++) {
      const module = await prisma.module.create({
        data: {
          name: `Module ${mod}`,
          description: `Description for Module-${mod}`,
          objectives: [
            `Objective 1 for module-${mod} Curriculum-${curr}`,
            `Objective 2 for module-${mod} Curriculum-${curr}`,
            `Objective 2 for module-${mod} Curriculum-${curr}`,
            `Objective 2 for module-${mod} Curriculum-${curr}`,
            `Objective 2 for module-${mod} Curriculum-${curr}`
          ],
          curriculum: {
            connect: {
              id: curriculum.id
            }
          }
        }
      })
      for (let un = 1; un <= iterator; un++) {
        const unit = await prisma.unit.create({
          data: {
            name: `Unit ${un}`,
            description: `Description for Unit-${un}`,
            objectives: [
              `Objective 1 for Unit-${un} Module-${mod} Curriculum-${curr}`,
              `Objective 1 for Unit-${un} Module-${mod} Curriculum-${curr}`,
              `Objective 1 for Unit-${un} Module-${mod} Curriculum-${curr}`,
              `Objective 1 for Unit-${un} Module-${mod} Curriculum-${curr}`,
              `Objective 1 for Unit-${un} Module-${mod} Curriculum-${curr}`
            ],
            moduleId: module.id
          }
        })
    this is how it's looking atm I can change it if it's too confusing. It produces the API responses that I added in the API responses section of the plan.
Auenc commented 1 year ago

I think it would be better if you split up the creation of each entity into their own functions. The you can call those functions in your for loops

mnmin commented 1 year ago

Ok, I'll split them