Skip to content

Commit

Permalink
lessons api updates
Browse files Browse the repository at this point in the history
  • Loading branch information
neil-sweetcodepro committed May 13, 2022
1 parent 44c5f09 commit b122976
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 50 deletions.
313 changes: 274 additions & 39 deletions api/src/controllers/lessonController.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
const { Joi } = require('express-validation')
const db = require('../models')
const BadRequestError = require('../errors/badRequestError')
const NotFoundError = require('../errors/notFoundError')
const ForbiddenRequestError = require('../errors/forbiddenRequestError')

const getCourseIdFromUrl = (baseUrl) => baseUrl.split('/')[3]

const getLessons = async (req, res) => {
const pageSize = 1
const page = Number(req.query.pageNumber) || 1
let { page, pageSize } = req.query
page = Number(page) || 1
pageSize = Number(pageSize) || undefined

const courseId = getCourseIdFromUrl(req.baseUrl)

const lessons = await db.Lesson.findAndCountAll({
// offset: (page - 1) * pageSize,
// limit: pageSize,
offset: pageSize === undefined ? undefined : (page - 1) * pageSize,
limit: pageSize === undefined ? undefined : pageSize,
order: [['createdAt', 'ASC']],
where: { courseId: req.params.courseId }
where: { courseId }
})

const totalPages = Math.ceil(lessons.count / pageSize)
const totalPages = Math.ceil(lessons.count / pageSize ?? 1)
res.status(200).json({
status: true,
message: 'fetched all lessons successfully',
lessons: lessons.rows.map(rec => ({ ...rec.dataValues })),
message: 'Fetched lessons successfully',
data: lessons.rows,
totalItems: lessons.count,
totalPages,
page,
pageSize
})
}

const getLessonById = async (req, res) => {
const { id } = req.params
const courseId = getCourseIdFromUrl(req.baseUrl)
const lesson = await db.Lesson.findOne({
where: { id },
where: { id, courseId },
include: [
{
model: db.RichText,
Expand All @@ -37,56 +48,279 @@ const getLessonById = async (req, res) => {
]
})

if (!lesson) {
throw new NotFoundError()
}

res.status(200).json({
status: true,
message: 'fetched lesson successfully',
message: 'Lesson fetched successfully',
data: lesson
})
}

const lessonSchema = {
body: Joi.object({
title: Joi.string().required(),
lessonOrder: Joi.number().required(),
textHeader: Joi.alternatives().try(
Joi.string(),
Joi.array().items(Joi.string())
),
textDescription: Joi.alternatives().try(
Joi.string(),
Joi.array().items(Joi.string())
),
photoDescription: Joi.alternatives().try(
Joi.string(),
Joi.array().items(Joi.string())
),
descriptionOrder: Joi.alternatives().try(
Joi.string().valid('text', 'image'),
Joi.array().items(Joi.string().valid('text', 'image'))
)
}),
}

const addLesson = async (req, res) => {
let coverImg = ''
if (req.file) {
coverImg = req.file.filename
const { baseUrl, user, files, body } = req
const { title, descriptionOrder, lessonOrder } = body
const courseId = getCourseIdFromUrl(baseUrl)
const thumbnail = files.thumbnail?.[0]?.filename || null

const course = await db.Courses.findByPk(courseId)

if (!course) {
throw new NotFoundError()
}

if (course.creatorId !== user.id) {
throw new ForbiddenRequestError()
}

const lessonData = {
courseId,
thumbnail: thumbnail ? `thumbnail/${thumbnail}` : null,
title,
order: lessonOrder
}
const { courseId, title, lessonDesc, richtextId } = req.body
const lesson = await db.Lesson.create({ courseId, title, lessonDesc, richtextId, coverImg })
let createdLessonId;

await db.sequelize.transaction(async (transaction) => {
const richtext = await db.RichText.create({}, { transaction })
const richtextId = richtext.id

if (descriptionOrder) {

const richtextCount = Array.isArray(body.textDescription) ? body.textDescription.length : 1
const photoCount = files.lessons?.length || 0

if (photoCount !== body.descriptionOrder.filter(i => i === 'image').length) {
throw new BadRequestError('Photo details don\'t match the ordering details')
}

if (richtextCount !== body.descriptionOrder.filter(i => i === 'text').length) {
throw new BadRequestError('Text details don\'t match the ordering details')
}

if (richtextCount > 1 && body.textDescription?.length !== body.textHeader?.length) {
throw new BadRequestError('Number of text header and description items do not match')
}

const orderCount = Array.isArray(descriptionOrder) ? descriptionOrder.length : 1

// validation if texts and photo tallies the order count
if (photoCount + richtextCount !== orderCount) {
throw new BadRequestError('Contents don\'t match the ordering count')
}

await Promise.all([...(orderCount === 1 ? [descriptionOrder] : descriptionOrder)].map(async (order, index) => {
if (order === 'text') {
return db.Text.create({
richtextId,
textHeading: Array.isArray(body.textHeader) ? body.textHeader.shift() : body.textHeader,
textDescription: Array.isArray(body.textDescription) ? body.textDescription.shift() : body.textDescription,
order: index + 1
}, {
transaction
})
}

return db.Photo.create({
richtextId,
lessonImg: `lessons/${files.lessons.shift().filename}`,
photoDescription: Array.isArray(body.photoDescription) ? body.photoDescription.shift() : body.photoDescription,
isImgDesc: false,
order: index + 1
}, {
transaction
})
}))
}

const createdLesson = await db.Lesson.create({
...lessonData,
richtextId
}, {
transaction
})

createdLessonId = createdLesson.id
})

const lesson = await db.Lesson.findOne({
where: { id: createdLessonId },
include: [
{
model: db.RichText,
include: [db.Text, db.Photo]
}
]
})

res.status(201).json({
status: true,
message: 'added new lesson successfully',
message: 'New lesson added successfully',
data: lesson
})
}

const deleteLesson = async (req, res) => {
const { id } = req.params
const lesson = await db.Lesson.destroy({
where: { id },
include: [db.Video, db.Photo, db.Text, db.Material]
const courseId = getCourseIdFromUrl(req.baseUrl)

const lesson = await db.Lesson.findOne({
where: { id, courseId },
include: [db.Courses]
})
res.status(202).json({
status: true,
message: 'deleted lesson successfully',
data: lesson

if (!lesson) {
throw new NotFoundError()
}

if (lesson.course.creatorId !== req.user.id) {
throw new ForbiddenRequestError()
}

await db.sequelize.transaction(async (transaction) => {
await lesson.destroy({
transaction
})
await db.RichText.destroy({
where: { id: lesson.richtextId },
transaction
})
})

res.status(200).json({
message: 'Lesson deleted successfully'
})
}

const updateLesson = async (req, res) => {
const { id } = req.params
let coverImg
if (req.file) {
coverImg = req.file.filename
const { params, baseUrl, body, user, files } = req
const { id } = params
const courseId = getCourseIdFromUrl(baseUrl)

let lesson = await db.Lesson.findOne({
where: { id, courseId },
include: [db.Courses]
})

if (!lesson) {
throw new NotFoundError()
}

if (lesson.course.creatorId !== user.id) {
throw new ForbiddenRequestError()
}

const { title, descriptionOrder, lessonOrder } = body
const thumbnail = files.thumbnail?.[0]?.filename || null
const lessonData = {
thumbnail: thumbnail ? `thumbnail/${thumbnail}` : null,
title,
order: lessonOrder
}
const oldRichtextId = lesson.richtextId

await db.sequelize.transaction(async (transaction) => {
const richtext = await db.RichText.create({}, { transaction })
const richtextId = richtext.id

if (descriptionOrder) {
const richtextCount = Array.isArray(body.textDescription) ? body.textDescription.length : 1
const photoCount = files.lessons?.length || 0

const lesson = await db.Lesson.update(
{ ...req.body, coverImg },
{
where: { id }
if (photoCount !== body.descriptionOrder.filter(i => i === 'image').length) {
throw new BadRequestError('Photo details don\'t match the ordering details')
}

if (richtextCount !== body.descriptionOrder.filter(i => i === 'text').length) {
throw new BadRequestError('Text details don\'t match the ordering details')
}

if (richtextCount > 1 && body.textDescription?.length !== body.textHeader?.length) {
throw new BadRequestError('Number of text header and description items do not match')
}

const orderCount = Array.isArray(descriptionOrder) ? descriptionOrder.length : 1

// validation if texts and photo tallies the order count
if (photoCount + richtextCount !== orderCount) {
throw new BadRequestError('Contents don\'t match the ordering count')
}

await Promise.all([...(orderCount === 1 ? [descriptionOrder] : descriptionOrder)].map(async (order, index) => {
if (order === 'text') {
return db.Text.create({
richtextId,
textHeading: Array.isArray(body.textHeader) ? body.textHeader.shift() : body.textHeader,
textDescription: Array.isArray(body.textDescription) ? body.textDescription.shift() : body.textDescription,
order: index + 1
}, {
transaction
})
}

return db.Photo.create({
richtextId,
lessonImg: `courses/${files.lessons.shift().filename}`,
photoDescription: Array.isArray(body.photoDescription) ? body.photoDescription.shift() : body.photoDescription,
isImgDesc: false,
order: index + 1
}, {
transaction
})
}))
}
)
res.status(202).json({
status: true,
message: 'lesson updated successfully',

await db.RichText.destroy({
where: {
id: oldRichtextId
},
}, {
transaction
})

await lesson.update({
...lessonData,
richtextId
}, {
transaction
})
})

lesson = await db.Lesson.findOne({
where: { id },
include: [
{
model: db.RichText,
include: [db.Text, db.Photo]
}
]
})

res.status(200).json({
message: 'Lesson updated successfully',
data: lesson
})
}
Expand All @@ -96,5 +330,6 @@ module.exports = {
getLessonById,
addLesson,
deleteLesson,
updateLesson
updateLesson,
lessonSchema
}
1 change: 1 addition & 0 deletions api/src/middleware/errorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { ValidationError } = require('express-validation')
const CustomError = require('../errors/customError')

const errorHandler = (err, _req, res, _next) => {
console.log(err)
if (err instanceof CustomError) {
return res.status(err.statusCode).send({ errors: err.serializeErrors() })
}
Expand Down
Loading

0 comments on commit b122976

Please sign in to comment.