Skip to content

Commit 9a26266

Browse files
committed
part 4b
1 parent 37f84fc commit 9a26266

12 files changed

Lines changed: 471 additions & 59 deletions

File tree

part4/blog/app.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const express = require("express");
2+
const app = express();
3+
require("express-async-error")
4+
const cors = require("cors");
5+
const mongoose = require("mongoose");
6+
const config = require("./utils/config");
7+
const blogRouter = require("./controllers/blog");
8+
const morgan = require("morgan")
9+
const middleware = require("./utils/middleware")
10+
11+
morgan.token("body", function (req, res) {
12+
return JSON.stringify(req.body);
13+
});
14+
15+
mongoose.connect(config.mongoUrl, {
16+
useNewUrlParser: true,
17+
useUnifiedTopology: true,
18+
useFindAndModify: false,
19+
useCreateIndex: true,
20+
});
21+
22+
app.use(cors());
23+
app.use(express.json());
24+
app.use(
25+
morgan(":method :url :status :res[content-length] - :response-time ms :body")
26+
);
27+
app.use("/api/blogs", blogRouter);
28+
app.use(middleware.unknownEndpoint)
29+
app.use(middleware.errorHandler)
30+
31+
module.exports = app;

part4/blog/controllers/blog.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,37 @@ const Blog = require("../models/blog");
33

44
blogRouter
55
.route("/")
6-
.get((req, res, next) => {
7-
Blog.find({})
8-
.then((blogs) => {
9-
res.json(blogs);
10-
})
11-
.catch((err) => next(err));
6+
.get(async (req, res) => {
7+
const blogs = await Blog.find({});
8+
res.json(blogs);
129
})
13-
.post((req, res, next) => {
14-
const blog = new Blog(req.body);
10+
.post(async (req, res) => {
11+
const { author, title, url, likes } = req.body;
1512

16-
blog
17-
.save()
18-
.then((result) => {
19-
res.status(201).json(result);
20-
})
21-
.catch((err) => next(err));
13+
if (!title || !url) res.status(400).end();
14+
15+
const blog = new Blog({
16+
author: author || "unknown",
17+
title,
18+
url,
19+
likes: likes || 0,
20+
});
21+
22+
const result = await blog.save();
23+
res.status(201).json(result);
24+
});
25+
26+
blogRouter
27+
.route("/:id")
28+
.delete(async (req, res) => {
29+
await Blog.findByIdAndRemove(req.params.id);
30+
res.status(204).end();
31+
})
32+
.put(async (req, res) => {
33+
const updatedBlog = await Blog.findByIdAndUpdate(req.params.id, req.body, {
34+
new: true,
35+
});
36+
res.json(updatedBlog);
2237
});
2338

2439
module.exports = blogRouter;

part4/blog/index.js

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
1-
const express = require("express");
2-
const app = express();
3-
const cors = require("cors");
4-
const mongoose = require("mongoose");
5-
const config = require("./utils/config");
6-
const blogRouter = require("./controllers/blog");
7-
const morgan = require("morgan")
8-
const middleware = require("./utils/middleware")
1+
const app = require("./app");
2+
const logger = require("./utils/logger")
3+
const config = require("./utils/config")
94

10-
morgan.token("body", function (req, res) {
11-
return JSON.stringify(req.body);
12-
});
13-
14-
mongoose.connect(config.mongoUrl, {
15-
useNewUrlParser: true,
16-
useUnifiedTopology: true,
17-
useFindAndModify: false,
18-
useCreateIndex: true,
19-
});
20-
21-
app.use(cors());
22-
app.use(express.json());
23-
app.use(
24-
morgan(":method :url :status :res[content-length] - :response-time ms :body")
25-
);
26-
app.use("/api/blog", blogRouter);
27-
app.use(middleware.unknownEndpoint)
28-
app.use(middleware.errorHandler)
295
app.listen(config.PORT, () => {
30-
console.log(`Server running on port ${config.PORT}`);
6+
logger.info(`Server running on port ${config.PORT}`);
317
});

part4/blog/jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
testEnvironment: "node",
3+
};

part4/blog/models/blog.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ const mongoose = require("mongoose");
22
const uniqueValidator = require("mongoose-unique-validator");
33

44
const blogSchema = new mongoose.Schema({
5-
title: { type: String, required: true, unique: true },
6-
author: { type: String, required: true, unique: true },
7-
url: { type: String, required: true, unique: true },
5+
title: { type: String },
6+
author: { type: String },
7+
url: { type: String },
88
likes: { type: Number },
99
});
1010

part4/blog/package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
"version": "1.0.0",
44
"main": "index.js",
55
"scripts": {
6-
"start": "node index.js",
7-
"dev": "nodemon index.js",
8-
"test": "jest --verbose"
6+
"start": "cross-env NODE_ENV=production node index.js",
7+
"dev": "cross-env NODE_ENV=development nodemon index.js",
8+
"test": "cross-env NODE_ENV=test jest --verbose --runInBand"
99
},
1010
"author": "Joseph Odunsi",
1111
"license": "MIT",
1212
"dependencies": {
1313
"cors": "^2.8.5",
14+
"cross-env": "^7.0.3",
1415
"dotenv": "^8.2.0",
1516
"express": "^4.17.1",
17+
"express-async-error": "^0.0.2",
1618
"mongoose": "^5.12.5",
17-
"morgan": "^1.10.0"
19+
"morgan": "^1.10.0",
20+
"supertest": "^6.1.3"
1821
},
1922
"devDependencies": {
2023
"mongoose-unique-validator": "^2.0.3"

part4/blog/test/api.test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
const mongoose = require("mongoose");
2+
const supertest = require("supertest");
3+
const app = require("../app");
4+
const Blog = require("../models/blog");
5+
const { initialBlogs, blogsInDb } = require("./test_helper");
6+
7+
const api = supertest(app);
8+
9+
beforeEach(async() => {
10+
await Blog.deleteMany({});
11+
await Blog.insertMany(initialBlogs);
12+
});
13+
14+
test("get blogs", async () => {
15+
const response = await api
16+
.get("/api/blogs")
17+
.expect(200)
18+
.expect("Content-Type", /application\/json/);
19+
20+
expect(response.body).toHaveLength(0);
21+
});
22+
23+
test("save a blog", async () => {
24+
const newBlog = {
25+
title: "Type wars",
26+
author: "Unknown",
27+
url: "http://blog.cleancoder.com/uncle-bob/2016/05/01/TypeWars.html",
28+
likes: 2,
29+
};
30+
31+
await api
32+
.post("/api/blogs")
33+
.send(newBlog)
34+
.expect(201)
35+
.expect("Content-Type", /application\/json/);
36+
37+
const res = await api.get("/api/blogs");
38+
expect(res.body).toHaveLength(initialBlogs.length + 1);
39+
const blogs = res.body.map((blog) => blog.title);
40+
expect(blogs).toContainEqual("Type wars");
41+
});
42+
43+
test("verify blog list has a unique identifier", async () => {
44+
const { body: blogs } = await api.get("/api/blogs");
45+
46+
expect(blogs.forEach((blog) => expect(blog.id).toBeDefined()));
47+
});
48+
49+
test("if the likes property is missing, default value to 0", async () => {
50+
const newBlog = {
51+
title: "Type wars",
52+
author: "Unknown",
53+
url: "http://blog.cleancoder.com/uncle-bob/2016/05/01/TypeWars.html",
54+
};
55+
56+
const { body: savedBlog } = await api
57+
.post("/api/blogs")
58+
.send(newBlog)
59+
.expect(201)
60+
.expect("Content-Type", /application\/json/);
61+
62+
expect(savedBlog.likes).toBe(0);
63+
});
64+
65+
test("return bad request if title and url is missing", async () => {
66+
const newBlog = {
67+
author: "John Doe",
68+
};
69+
70+
const response = await api.post("/api/blogs").send(newBlog);
71+
expect(response.statusCode).toBe(400);
72+
73+
const {body:blogs} = await api.get("/api/blogs")
74+
expect(blogs).toHaveLength(initialBlogs.length)
75+
});
76+
77+
test("confirm deletion of a blog", async()=>{
78+
const blogsAtStart = await blogsInDb()
79+
const blogToDelete = blogsAtStart[0]
80+
81+
const response = await api.delete(`/api/blogs/${blogToDelete.id}`)
82+
expect(response.status).toBe(204)
83+
84+
const {body:blogsAtEnd} = await api.get("/api/blogs")
85+
expect(blogsAtEnd).toHaveLength(initialBlogs.length - 1)
86+
const contents = blogsAtEnd.map(blog => blog.title)
87+
88+
expect(contents).not.toContain(blogToDelete.title)
89+
})
90+
91+
test("update a blog", async()=>{
92+
const blogsAtStart = await blogsInDb();
93+
const {title, author, url, id} = blogsAtStart[0]
94+
95+
const blog = {title, url, author, likes: 30}
96+
97+
const {body:updatedBlog} = await api.put(`/api/blogs/${id}`).send(blog).expect(200)
98+
99+
expect(updatedBlog.likes).toBe(30)
100+
})
101+
102+
afterAll(async (done) => {
103+
await mongoose.connection.close();
104+
done();
105+
});

part4/blog/test/test_helper.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const Blog = require("../models/blog")
2+
const initialBlogs = [
3+
{
4+
title: "Canonical string reduction",
5+
author: "Edsger W. Dijkstra",
6+
url: "http://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD808.html",
7+
likes: 12,
8+
},
9+
{
10+
title: "TDD harms architecture",
11+
author: "Robert C. Martin",
12+
url:
13+
"http://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html",
14+
likes: 0
15+
},
16+
];
17+
18+
const nonExistingId = async () => {
19+
const blog = new Blog({
20+
title: "Typescript",
21+
author: "unknown",
22+
url:
23+
"http://linktotypescriptblog.com",
24+
likes: 13,
25+
});
26+
await blog.save();
27+
await blog.remove();
28+
29+
return blog._id.toString();
30+
};
31+
32+
const blogsInDb = async () => {
33+
const blogs = await Blog.find({});
34+
return blogs.map((blog) => blog.toJSON());
35+
};
36+
37+
module.exports = {
38+
initialBlogs,
39+
nonExistingId,
40+
blogsInDb,
41+
};

part4/blog/utils/config.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
require("dotenv").config();
2-
const mongoUrl = process.env.MONGO_URL;
2+
const mongoUrl =
3+
process.env.NODE_ENV === "test"
4+
? process.env.TEST_MONGO_URL
5+
: process.env.MONGO_URL;
36
const PORT = process.env.PORT;
47

5-
module.exports = { mongoUrl , PORT};
8+
module.exports = { mongoUrl, PORT };

part4/blog/utils/logger.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
const info = (...params) => console.log(...params);
2-
const error = (...params) => console.error(...params);
1+
require("dotenv").config();
2+
const info = (...params) => {
3+
if (process.env.NODE_ENV !== "test") {
4+
console.log(...params);
5+
}
6+
};
7+
const error = (...params) => {
8+
if (process.env.NODE_ENV !== "test") {
9+
console.log(...params);
10+
}
11+
};
312

413
module.exports = { info, error };

0 commit comments

Comments
 (0)