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 |