diff --git a/README.md b/README.md index a28f3814f..29d3937ca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Acebook +# Trello Board: + +https://trello.com/b/Ckf68HAB/acebook + In this project, you are tasked with working on an existing application. A significant part of the challenge will be to familiarise yourself with the codebase you've inherited, as you work to **improve and extend** it. ## Videos diff --git a/api/app.js b/api/app.js index 07aa00b3b..61d37e931 100644 --- a/api/app.js +++ b/api/app.js @@ -7,11 +7,12 @@ const JWT = require("jsonwebtoken"); const postsRouter = require("./routes/posts"); const tokensRouter = require("./routes/tokens"); const usersRouter = require("./routes/users"); +const homeRouter = require("./routes/home"); const app = express(); // setup for receiving JSON -app.use(express.json()) +app.use(express.json()); app.use(logger("dev")); app.use(express.json()); @@ -19,18 +20,17 @@ app.use(express.static(path.join(__dirname, "public"))); // middleware function to check for valid tokens const tokenChecker = (req, res, next) => { - let token; - const authHeader = req.get("Authorization") + const authHeader = req.get("Authorization"); - if(authHeader) { - token = authHeader.slice(7) + if (authHeader) { + token = authHeader.slice(7); } JWT.verify(token, process.env.JWT_SECRET, (err, payload) => { - if(err) { - console.log(err) - res.status(401).json({message: "auth error"}); + if (err) { + console.log(err); + res.status(401).json({ message: "auth error" }); } else { req.user_id = payload.user_id; next(); @@ -42,6 +42,7 @@ const tokenChecker = (req, res, next) => { app.use("/posts", tokenChecker, postsRouter); app.use("/tokens", tokensRouter); app.use("/users", usersRouter); +app.use("/", homeRouter); // catch 404 and forward to error handler app.use((req, res, next) => { @@ -55,7 +56,7 @@ app.use((err, req, res) => { res.locals.error = req.app.get("env") === "development" ? err : {}; // respond with details of the error - res.status(err.status || 500).json({message: 'server error'}) + res.status(err.status || 500).json({ message: "server error" }); }); module.exports = app; diff --git a/api/controllers/posts.js b/api/controllers/posts.js index dc487d2dd..331b6af1d 100644 --- a/api/controllers/posts.js +++ b/api/controllers/posts.js @@ -7,19 +7,21 @@ const PostsController = { if (err) { throw err; } - const token = await TokenGenerator.jsonwebtoken(req.user_id) + const token = await TokenGenerator.jsonwebtoken(req.user_id); res.status(200).json({ posts: posts, token: token }); - }); + }).sort({ time: -1 }); }, Create: (req, res) => { + req.body.time = Date.now(); + req.body.posterUserId = req.user_id; const post = new Post(req.body); post.save(async (err) => { if (err) { throw err; } - const token = await TokenGenerator.jsonwebtoken(req.user_id) - res.status(201).json({ message: 'OK', token: token }); + const token = await TokenGenerator.jsonwebtoken(req.user_id); + res.status(201).json({ message: "OK", token: token }); }); }, }; diff --git a/api/controllers/tokens.js b/api/controllers/tokens.js index cc983053a..60336a18e 100644 --- a/api/controllers/tokens.js +++ b/api/controllers/tokens.js @@ -1,25 +1,24 @@ const User = require("../models/user"); -const TokenGenerator = require("../models/token_generator") +const TokenGenerator = require("../models/token_generator"); const SessionsController = { - Create: (req, res) => { const email = req.body.email; const password = req.body.password; User.findOne({ email: email }).then(async (user) => { if (!user) { - console.log("auth error: user not found") + console.log("auth error: user not found"); res.status(401).json({ message: "auth error" }); } else if (user.password !== password) { - console.log("auth error: passwords do not match") + console.log("auth error: passwords do not match"); res.status(401).json({ message: "auth error" }); } else { - const token = await TokenGenerator.jsonwebtoken(user.id) + const token = await TokenGenerator.jsonwebtoken(user.id); res.status(201).json({ token: token, message: "OK" }); } }); - } + }, }; module.exports = SessionsController; diff --git a/api/controllers/users.js b/api/controllers/users.js index 8f195d29e..79a6e51d9 100644 --- a/api/controllers/users.js +++ b/api/controllers/users.js @@ -5,9 +5,9 @@ const UsersController = { const user = new User(req.body); user.save((err) => { if (err) { - res.status(400).json({message: 'Bad request'}) + res.status(400).json({ message: "Bad request" }); } else { - res.status(201).json({ message: 'OK' }); + res.status(201).json({ message: "OK" }); } }); }, diff --git a/api/models/post.js b/api/models/post.js index 6c4e213e9..341813064 100644 --- a/api/models/post.js +++ b/api/models/post.js @@ -1,7 +1,9 @@ const mongoose = require("mongoose"); const PostSchema = new mongoose.Schema({ - message: String + message: String, + time: Number, + posterUserId: String }); const Post = mongoose.model("Post", PostSchema); diff --git a/api/models/token_generator.js b/api/models/token_generator.js index 0ec5a0d0c..ad532349c 100644 --- a/api/models/token_generator.js +++ b/api/models/token_generator.js @@ -1,10 +1,10 @@ const JWT = require("jsonwebtoken"); -const options = {expiresIn: "10m"}; +const options = { expiresIn: "10m" }; const secret = process.env.JWT_SECRET; class TokenGenerator { static jsonwebtoken(user_id) { - return JWT.sign({user_id: user_id, iat: Date.now()}, secret, options); + return JWT.sign({ user_id: user_id, iat: Date.now() }, secret, options); } } diff --git a/api/package.json b/api/package.json index 48cd9be58..6c513d324 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "nodemon ./bin/www", "start:test": "MONGODB_URL='mongodb://0.0.0.0/acebook_test' npm start", - "test": "jest" + "test": "jest --runInBand" }, "engines": { "node": ">=18.1.0" diff --git a/api/spec/controllers/posts.spec.js b/api/spec/controllers/posts.spec.js index 9f1e67ec3..ace050779 100644 --- a/api/spec/controllers/posts.spec.js +++ b/api/spec/controllers/posts.spec.js @@ -1,27 +1,28 @@ const app = require("../../app"); const request = require("supertest"); require("../mongodb_helper"); -const Post = require('../../models/post'); -const User = require('../../models/user'); -const TokenGenerator = require('../../models/token_generator'); +const Post = require("../../models/post"); +const User = require("../../models/user"); +const TokenGenerator = require("../../models/token_generator"); const JWT = require("jsonwebtoken"); let token; describe("/posts", () => { - beforeAll( async () => { - const user = new User({email: "test@test.com", password: "12345678"}); + beforeAll(async () => { + const user = new User({ email: "test@test.com", password: "12345678" }); + userIdTest = user.id await user.save(); token = TokenGenerator.jsonwebtoken(user.id); }); - beforeEach( async () => { + beforeEach(async () => { await Post.deleteMany({}); - }) + }); - afterAll( async () => { + afterAll(async () => { await User.deleteMany({}); await Post.deleteMany({}); - }) + }); describe("POST, when token is present", () => { test("responds with a 201", async () => { @@ -31,7 +32,7 @@ describe("/posts", () => { .send({ message: "hello world", token: token }); expect(response.status).toEqual(201); }); - + test("creates a new post", async () => { await request(app) .post("/posts") @@ -41,18 +42,41 @@ describe("/posts", () => { expect(posts.length).toEqual(1); expect(posts[0].message).toEqual("hello world"); }); - + + test("creates a new post and adds in the time", async () => { + const time = Date.now(); + await request(app) + .post("/posts") + .set("Authorization", `Bearer ${token}`) + .send({ message: "hello world", token: token }); + let posts = await Post.find(); + expect(posts.length).toEqual(1); + expect(posts[0].message).toEqual("hello world"); + expect(posts[0].time / 10000).toBeCloseTo(time / 10000); + }); + + test("creates a new post and adds in the userID", async () => { + await request(app) + .post("/posts") + .set("Authorization", `Bearer ${token}`) + .send({ message: "hello world", token: token }); + let posts = await Post.find(); + expect(posts.length).toEqual(1); + expect(posts[0].message).toEqual("hello world"); + expect(posts[0].posterUserId).toEqual(userIdTest); + }); + test("returns a new token", async () => { let response = await request(app) .post("/posts") .set("Authorization", `Bearer ${token}`) - .send({ message: "hello world", token: token }) + .send({ message: "hello world", token: token }); let newPayload = JWT.decode(response.body.token, process.env.JWT_SECRET); let originalPayload = JWT.decode(token, process.env.JWT_SECRET); expect(newPayload.iat > originalPayload.iat).toEqual(true); - }); + }); }); - + describe("POST, when token is missing", () => { test("responds with a 401", async () => { let response = await request(app) @@ -60,93 +84,101 @@ describe("/posts", () => { .send({ message: "hello again world" }); expect(response.status).toEqual(401); }); - + test("a post is not created", async () => { - await request(app) - .post("/posts") - .send({ message: "hello again world" }); + await request(app).post("/posts").send({ message: "hello again world" }); let posts = await Post.find(); expect(posts.length).toEqual(0); }); - + test("a token is not returned", async () => { let response = await request(app) .post("/posts") .send({ message: "hello again world" }); expect(response.body.token).toEqual(undefined); }); - }) + }); describe("GET, when token is present", () => { test("returns every post in the collection", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); let response = await request(app) .get("/posts") .set("Authorization", `Bearer ${token}`) - .send({token: token}); - let messages = response.body.posts.map((post) => ( post.message )); + .send({ token: token }); + let messages = response.body.posts.map((post) => post.message); expect(messages).toEqual(["howdy!", "hola!"]); - }) + }); test("the response code is 200", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); let response = await request(app) .get("/posts") .set("Authorization", `Bearer ${token}`) - .send({token: token}); + .send({ token: token }); expect(response.status).toEqual(200); - }) + }); + + test("returns every post in the collection in reverse time order", async () => { + let post1 = new Post({ message: "howdy!", time: 1669897643479 }); + let post2 = new Post({ message: "hola!", time: 1669897643480 }); + await post1.save(); + await post2.save(); + let response = await request(app) + .get("/posts") + .set("Authorization", `Bearer ${token}`) + .send({ token: token }); + let messages = response.body.posts.map((post) => post.message); + expect(messages).toEqual(["hola!", "howdy!"]); + }); test("returns a new token", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); let response = await request(app) .get("/posts") .set("Authorization", `Bearer ${token}`) - .send({token: token}); + .send({ token: token }); let newPayload = JWT.decode(response.body.token, process.env.JWT_SECRET); let originalPayload = JWT.decode(token, process.env.JWT_SECRET); expect(newPayload.iat > originalPayload.iat).toEqual(true); - }) - }) + }); + }); describe("GET, when token is missing", () => { test("returns no posts", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); - let response = await request(app) - .get("/posts"); + let response = await request(app).get("/posts"); expect(response.body.posts).toEqual(undefined); - }) + }); test("the response code is 401", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); - let response = await request(app) - .get("/posts"); + let response = await request(app).get("/posts"); expect(response.status).toEqual(401); - }) + }); test("does not return a new token", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); - let response = await request(app) - .get("/posts"); + let response = await request(app).get("/posts"); expect(response.body.token).toEqual(undefined); - }) - }) + }); + }); }); diff --git a/api/spec/controllers/users.spec.js b/api/spec/controllers/users.spec.js index adccba0b6..5855635b9 100644 --- a/api/spec/controllers/users.spec.js +++ b/api/spec/controllers/users.spec.js @@ -22,6 +22,8 @@ describe("/users", () => { .send({email: "scarlett@email.com", password: "1234"}) let users = await User.find() let newUser = users[users.length - 1] + console.log(users) + console.log(newUser.email) expect(newUser.email).toEqual("scarlett@email.com") }) }) diff --git a/api/spec/models/post.spec.js b/api/spec/models/post.spec.js index 3acfd48ce..3407c6fab 100644 --- a/api/spec/models/post.spec.js +++ b/api/spec/models/post.spec.js @@ -37,4 +37,22 @@ describe("Post model", () => { }); }); }); + + it("can save a post with userid", (done) => { + var post = new Post({ message: "some message", posterUserId: "12345" }); + + post.save((err) => { + expect(err).toBeNull(); + + Post.find((err, posts) => { + expect(err).toBeNull(); + + expect(posts[0]).toMatchObject({ + message: "some message", + posterUserId: "12345", + }); + done(); + }); + }); + }); }); diff --git a/frontend/cypress.config.js b/frontend/cypress.config.js index 2e2eb5a8c..fa82fc97d 100644 --- a/frontend/cypress.config.js +++ b/frontend/cypress.config.js @@ -12,6 +12,6 @@ module.exports = defineConfig({ setupNodeEvents(on, config) { // implement node event listeners here }, - baseUrl: 'http://localhost:3000' + baseUrl: "http://localhost:3000", }, }); diff --git a/frontend/cypress/e2e/home.cy.js b/frontend/cypress/e2e/home.cy.js new file mode 100644 index 000000000..0c26f91ea --- /dev/null +++ b/frontend/cypress/e2e/home.cy.js @@ -0,0 +1,14 @@ +describe("Homepage", () => { + it("redirects to '/login' when login button is pressed", () => { + cy.visit('/'); + cy.get('[href="/login"] > button').click(); + + cy.url().should("include", "/login"); + }) + it("redirects to '/signup' when sign up button is pressed", () => { + cy.visit('/'); + cy.get('[href="/signup"] > button').click(); + + cy.url().should("include", "/signup"); + }) +}) \ No newline at end of file diff --git a/frontend/cypress/e2e/making_a_post.cy.js b/frontend/cypress/e2e/making_a_post.cy.js index e69de29bb..d2572e97a 100644 --- a/frontend/cypress/e2e/making_a_post.cy.js +++ b/frontend/cypress/e2e/making_a_post.cy.js @@ -0,0 +1,12 @@ +describe("making a post", () => { + before(() => { + cy.signup("user@email.com", "12345678"); + }); + + it("can log in and create a post", () => { + cy.visit("/login"); + cy.get("#email").type("user@email.com"); + cy.get("#password").type("12345678"); + cy.get("#submit").click(); + }); +}); diff --git a/frontend/cypress/e2e/making_a_post_on_page.cy.js b/frontend/cypress/e2e/making_a_post_on_page.cy.js new file mode 100644 index 000000000..790aba562 --- /dev/null +++ b/frontend/cypress/e2e/making_a_post_on_page.cy.js @@ -0,0 +1,26 @@ +describe("Making a post", () => { + before(() => { + cy.signup("user@email.com", "12345678"); + }); + + it("when signed-in when can make a post", () => { + cy.visit("/login"); + cy.get("#email").type("someone@example.com"); + cy.get("#password").type("password"); + cy.get("#submit").click(); + cy.visit("/new_post"); + cy.get("#content").type("this is a post"); + cy.get("#submit").click(); + + cy.url().should("include", "/posts"); + }); + + it("redirects to login when not signed-in", () => { + + cy.visit("/new_post"); + cy.get("#content").type("this is a post"); + cy.get("#submit").click(); + + cy.url().should("include", "/login"); + }); +}); diff --git a/frontend/cypress/e2e/signing_in.cy.js b/frontend/cypress/e2e/signing_in.cy.js index 93fe6bab3..aca20b90e 100644 --- a/frontend/cypress/e2e/signing_in.cy.js +++ b/frontend/cypress/e2e/signing_in.cy.js @@ -1,8 +1,7 @@ describe("Signing in", () => { - before(() => { - cy.signup("user@email.com", "12345678") - }) + cy.signup("user@email.com", "12345678"); + }); it("with valid credentials, redirects to '/posts'", () => { cy.visit("/login"); @@ -28,4 +27,4 @@ describe("Signing in", () => { cy.url().should("include", "/login"); }); -}); \ No newline at end of file +}); diff --git a/frontend/cypress/e2e/signing_up.cy.js b/frontend/cypress/e2e/signing_up.cy.js index 747f70c89..77bc5cc75 100644 --- a/frontend/cypress/e2e/signing_up.cy.js +++ b/frontend/cypress/e2e/signing_up.cy.js @@ -23,4 +23,4 @@ describe("Signing up", () => { cy.url().should("include", "/signup"); }); -}); \ No newline at end of file +}); diff --git a/frontend/cypress/screenshots/signing_in.cy.js/Signing in -- with valid credentials, redirects to 'posts' (failed).png b/frontend/cypress/screenshots/signing_in.cy.js/Signing in -- with valid credentials, redirects to 'posts' (failed).png new file mode 100644 index 000000000..f7a3f4716 Binary files /dev/null and b/frontend/cypress/screenshots/signing_in.cy.js/Signing in -- with valid credentials, redirects to 'posts' (failed).png differ diff --git a/frontend/cypress/screenshots/signing_up.cy.js/Signing up -- with valid credentials, redirects to 'login' (failed).png b/frontend/cypress/screenshots/signing_up.cy.js/Signing up -- with valid credentials, redirects to 'login' (failed).png new file mode 100644 index 000000000..83c205ebb Binary files /dev/null and b/frontend/cypress/screenshots/signing_up.cy.js/Signing up -- with valid credentials, redirects to 'login' (failed).png differ diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js index e765fd7ee..516f91a7c 100644 --- a/frontend/cypress/support/commands.js +++ b/frontend/cypress/support/commands.js @@ -10,12 +10,12 @@ // // // -- This is a parent command -- -Cypress.Commands.add('signup', (email, password) => { +Cypress.Commands.add("signup", (email, password) => { cy.visit("/signup"); cy.get("#email").type(email); cy.get("#password").type(password); cy.get("#submit").click(); -}) +}); // // // -- This is a child command -- @@ -27,4 +27,4 @@ Cypress.Commands.add('signup', (email, password) => { // // // -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/frontend/cypress/support/component-index.html b/frontend/cypress/support/component-index.html index ac6e79fd8..e39ba4296 100644 --- a/frontend/cypress/support/component-index.html +++ b/frontend/cypress/support/component-index.html @@ -1,12 +1,12 @@
- - - + + +