본문 바로가기

notAnymore/Udemy API

Course CRUD [TDD, express]

Course

ModelSchema

const mongoose = require('mongoose');
const slugify = require('slugify');

const CourseSchema = new mongoose.Schema(
	{
		name: {
			type: String,
			required: [true, 'Please add a name'],
			unique: true,
			trim: true,
			maxlength: [50, 'Name can not be more than 50 characters']
		},
		slug: String,
		description: {
			type: String,
			required: [true, 'Please add a description'],
			maxlength: [500, 'Description can not be more than 500 characters']
		},
		photo: {
			type: String,
			default: 'no-photo.jgp'
		},
		user: {	// If logined, it will be filled automatically 
			type: mongoose.Schema.ObjectId,
			ref: 'User',
			required: true
		}		
	}
);

CourseSchema.pre('save', function(next) {
	this.slug = slugify(this.name, { lower: true });
	next();
});

module.exports = mongoose.model('Course', CourseSchema);

Requirement to work with "Course"

CREATE/POST : login with role as 'instructor'.

READ/GET : don't need. anyone can access.

UPDATE/PUT : login with role as 'instructor'.

DELETE : login with role as 'instructor'.

CREATE Course/POST

Process: Root -> Login : Save token in cookie 
-> GET createCourse(get getCreateCourse form) -> fill the form and submit 
-> POST postCreateCourse

1. To create 'course' client need to login and have role 'instructor'.

We did put protect() and authentication() methods before POST createCourse() in the route.

router
    .route('/api/v1/course')
    .get(protect, authorize('instructor', 'admin'), getCreateCourse);
    .post(protect, authorize('instructor', 'admin'), postCreateCourse);

"protect()" middleware checks the 'request header' whether it has token and the token is valid. and then verify the code to get the user data.

"autorize()" middleware checks the user data (passed from the "protect()" method) if It has the parameters(role 'instructor' or 'admin')

protect, authorize middleware

exports.protect = asyncHandler(async (req, res, next) => {
  let token;

  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith('Bearer')
  ) {
    // Set token from Bearer token in header
    token = req.headers.authorization.split(' ')[1];
    // Set token from cookie
  }
  // else if (req.cookies.token) {
  //   token = req.cookies.token;
  // }

  // Make sure token exists
  if (!token) {
    return next(new ErrorResponse('Not authorized to access this route', 401));
  }

  try {
    // Verify token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id);

    next();
  } catch (err) {
    return next(new ErrorResponse('Not authorized to access this route', 401));
  }
});

// Grant access to specific roles
exports.authorize = (...roles) => {
    return (req, res, next) => {
      if (!roles.includes(req.user.role)) {
        return next(
          new ErrorResponse(
            `User role ${req.user.role} is not authorized to access this route`,
            403
          )
        );
      }
      next();
    };
  };

TEST CODE createCourse

describe("api/v1/auth", () => {
    beforeEach(async() => {
        await User.deleteMany({});
        await Course.deleteMany({});
        
        let user1 = {
            name: "test1",
            email: "test1@gmail.com",
            role: "instructor",
            password: "123456"
        };

        let user2 = {
            name: "test2",
            email: "test2@gmail.com",
            role: "user",
            password: "123456"
        }

        await User.create(user1, user2);

        const instructorRes = await request(app)
            .post('/api/v1/auth/login')
            .send({ email: "test1@gmail.com", password: "123456" });
        expect(instructorRes.status).to.be.equal(200);
        expect(instructorRes.body).to.have.property("token");
        
        const userRes = await request(app)
            .post('/api/v1/auth/login')
            .send({ email: "test2@gmail.com", password: "123456" });
        expect(userRes.status).to.be.equal(200);
        expect(userRes.body).to.have.property("token");

        request.instructorToken = instructorRes.body.token;
        request.userToken = userRes.body.token;
    });


    describe("POST /", () => {
        it("[Success] create a course", async () => {
            const token = request.instructorToken; 

            // It is req.body to be sent postCreateCourse
            const course = {
                name: "course1",
                description: "desc",
                // user: user.id
            }

            const res = await request(app)
                .post('/api/v1/course/create')
                .set('Authorization', `Bearer ${token}`)
                .send(course)
                .expect(201);      //
            console.log(res.body.data);
            expect(res.body.data.name).to.be.equal('course1');
        });

        it("[Fail] create a course", async () => {
            const token = request.userToken; 

            // It is req.body to be sent postCreateCourse
            const course = {
                name: "course2",
                description: "desc",
                // user: user.id
            }

            const res = await request(app)
                .post('/api/v1/course/create')
                .set('Authorization', `Bearer ${token}`)
                .send(course);
            expect(res.status).to.be.equal(403);
            expect(res.body.error).to.be.equal('only instructor can create course');
        });
    })
})

Test fails. and then make "createCourse" code in '/controllers/course'

exports.createCourse = asyncCHandler(async(req, res, next) => {
    // got passed 'req.user' from protect middleware
    req.body.user = req.user.id
    
    const user await Course.create(req.body);
    
    res.status(201).JSON({
        success: ture,
        data: course
    });
});

READ Course/GET

Root -> GET getCourses -> GET Course(Course detail)

Anyone can accss to getting list of course and each course detail.

router
    .route('api/v1/courses')
    .get(getCourses);
    
router
    .route('/api/v1/courses/:id')
    .get(getCourse)

 

TEST CODE getCourses, getCourse

describe("GET /", async () => {
        it("[Success] get course list", async () => {
            const token = request.instructorToken; 

            // It is req.body to be sent postCreateCourse
            const course = {
                name: "course1",
                description: "desc",
                // user: user.id
            }

            const createRes = await request(app)
                .post('/api/v1/course/create')
                .set('Authorization', `Bearer ${token}`)
                .send(course)
                .expect(201);
            
            const res = await request(app)
                .get("/api/v1/course");
            expect(res.status).to.be.equal(200);
            //console.log(res.body.data[0]._id);
        })
    })

    describe("GET /:id", async () => {
        it("[Success] get a course detail", async () => {
            const token = request.instructorToken; 

            // It is req.body to be sent postCreateCourse
            const course = {
                name: "course1",
                description: "desc",
                // user: user.id
            }

            const createRes = await request(app)
                .post('/api/v1/course/create')
                .set('Authorization', `Bearer ${token}`)
                .send(course)
                .expect(201);


            const id = createRes.body.data._id;
            
            const res = await request(app)
                .get(`/api/v1/course/${id}`);
            expect(res.status).to.be.equal(200);
            console.log(res.body);
        });
    })

Test fails, so then make getCourses, getCourse code

exports.getCourses = asyncHandler(async (req, res, next) => {
    const courses = await Course.find({});
    
    res.status(200).json({
    	success: true,
        data: courses
    });

exports.getCourse = asyncHandler(async (req, res, next) => {
    const course = await Course.findById(req.params.id);
    
    res.status(200).json({
    	success: true,
        data: course
    });
});

UPDATE Course/PUT

Root -> Login : Save token in cookie
-> GET getCourse(Course detail) : 
-> PUT updateCourse

1. To create 'update', client need to login and user of request and owner of course should be same.

So we did put protect() and authentication() methods before POST createCourse() in the route.

router
    .route("/api/v1/course)
    .put(protect, authorize, updateCourse);

TEST CODE updateCourse

describe("/api/v1/course", async () => {
    beforeEach() // check it createCourse test code
    };
    
    describe("PUT /:id", async() => {
        it("[Success] update a course", async () => {
            const token = request.instructorToken; 

            // It is req.body to be sent postCreateCourse
            const course = {
                name: "course1",
                description: "desc",
                // user: user.id
            }

            const createRes = await request(app)
                .post('/api/v1/course/create')
                .set('Authorization', `Bearer ${token}`)
                .send(course)
                .expect(201);
                        
            const id = createRes.body.data._id;
            const changingData = {
                name: "changedName"
            };

            const res = await request(app)
                .put('/api/v1/course/'+id)
                .set('Authorization', `Bearer ${token}`)
                .send(changingData);
            expect(res.status).to.be.equal(200);
        })
    })
});

Test fails, then make updateCourse code in "contollers/course"

exports.updateCourse = asyncHandler(async(req, res, next) => {
    let course = await Course.findById(req.params.id);

    if (!course) {
        return next(
            new ErrorResponse('course not found')
        );
    }

    course = await Course.findByIdAndUpdate(req.params.id, req.body, {
        new: true,
        runValidators: true
    });

    res.status(200).json({
        success: true,
        data: course
    })
})

DELETE Course

Root -> Login: Save token in cookie
-> GET getCourse(Course detail)
-> DELETE deleteCourse
router
    .route('/api/v1/course/:id')
    .delete(deleteCourse);

TEST CODE deleteCourse

    describe("DELETE /:id", async() => {
        it("[Success] delete a course", async () => {
            const token = request.instructorToken; 

            // It is req.body to be sent postCreateCourse
            const course = {
                name: "course1",
                description: "desc",
                // user: user.id
            }

            const createRes = await request(app)
                .post('/api/v1/course/create')
                .set('Authorization', `Bearer ${token}`)
                .send(course)
                .expect(201);
                        
            const id = createRes.body.data._id;

            const res = await request(app)
                .delete('/api/v1/course/'+id)
                .set('Authorization', `Bearer ${token}`)
            expect(res.status).to.be.equal(200);
        })
    })

Test fails, then make deleteCourse code in "contollers/course"

exports.deleteCourse = asyncHandler(async(req, res, next) => {
    let course = await Course.findById(req.params.id);

    if (!course) {
        return next(
            new ErrorResponse('course not found')
        );
    }

    await Course.findByIdAndDelete(req.params.id);

    res.status(200).json({
        success: true,
        data: {}
    })
})

we are done with Course. it works. (but needs refactoring..)

see you soon with Content CRUD.

'notAnymore > Udemy API' 카테고리의 다른 글

Occured Errors [TDD, Express]  (0) 2020.05.25
Make authentication API with TDD [express]  (0) 2020.05.25