From f6dde51eda16a47f2c3bbc2cfdb31a365f86e1c3 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 10:09:10 +0100 Subject: [PATCH 01/25] Project cloned --- .env.example | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 60f3a816..00000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -DATABASE_URL="YOUR_DB_URL" - -# We need the following URL environment variable for test purposes: -# - TEST_DATABASE_URL must be a **completely separate** database from any other used in this file - -TEST_DATABASE_URL="YOUR_TEST_DB_URL" From 5cdb74d417a02e25129e9e56fd6351bcb6341e36 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 11:00:50 +0100 Subject: [PATCH 02/25] Get all movies endpoint --- src/controllers/customer.js | 1 + src/controllers/movies.js | 15 +++++++++++++++ src/domains/customer.js | 1 + src/domains/movies.js | 10 ++++++++++ src/errors/errors.js | 0 src/routers/customer.js | 4 +++- src/routers/movies.js | 10 ++++++++++ src/server.js | 2 ++ 8 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/controllers/movies.js create mode 100644 src/domains/movies.js create mode 100644 src/errors/errors.js create mode 100644 src/routers/movies.js diff --git a/src/controllers/customer.js b/src/controllers/customer.js index 775cfb42..dcfa7f21 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -43,6 +43,7 @@ const createCustomer = async (req, res) => { } } + module.exports = { createCustomer } diff --git a/src/controllers/movies.js b/src/controllers/movies.js new file mode 100644 index 00000000..3d017fa2 --- /dev/null +++ b/src/controllers/movies.js @@ -0,0 +1,15 @@ +const { PrismaClientKnownRequestError } = require("@prisma/client") +const { getMoviesDb } = require('../domains/movies.js') + + +async function getMovies(req, res) { + console.log('in') + const movies = await getMoviesDb() + res.status(200).json({ movies }) + } + + + + module.exports = { + getMovies + } \ No newline at end of file diff --git a/src/domains/customer.js b/src/domains/customer.js index c7f315fd..48b9fd00 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -21,6 +21,7 @@ const createCustomerDb = async (name, phone, email) => await prisma.customer.cre } }) + module.exports = { createCustomerDb } diff --git a/src/domains/movies.js b/src/domains/movies.js new file mode 100644 index 00000000..5feaad24 --- /dev/null +++ b/src/domains/movies.js @@ -0,0 +1,10 @@ +const prisma = require("../utils/prisma"); + +async function getMoviesDb() { + const movies = await prisma.movie.findMany(); + return movies; +} + +module.exports = { + getMoviesDb +}; diff --git a/src/errors/errors.js b/src/errors/errors.js new file mode 100644 index 00000000..e69de29b diff --git a/src/routers/customer.js b/src/routers/customer.js index f14a87fc..2abc72ea 100644 --- a/src/routers/customer.js +++ b/src/routers/customer.js @@ -1,6 +1,6 @@ const express = require("express"); const { - createCustomer + createCustomer, getMovies } = require('../controllers/customer'); const router = express.Router(); @@ -10,4 +10,6 @@ const router = express.Router(); // that looks like http://localhost:4040/customer/register router.post("/register", createCustomer); + + module.exports = router; diff --git a/src/routers/movies.js b/src/routers/movies.js new file mode 100644 index 00000000..39044989 --- /dev/null +++ b/src/routers/movies.js @@ -0,0 +1,10 @@ +const express = require("express"); +const router = express.Router() +const { + getMovies +} = require('../controllers/movies'); + +router.get("/", getMovies) + + +module.exports = router; diff --git a/src/server.js b/src/server.js index 93d47a16..f90d2d5e 100644 --- a/src/server.js +++ b/src/server.js @@ -15,7 +15,9 @@ app.use(express.urlencoded({ extended: true })); // Tell express to use your routers here const customerRouter = require('./routers/customer'); +const movieRouter = require('./routers/movies') app.use('/customers', customerRouter); +app.use('/movies', movieRouter); module.exports = app From eef11492ecf7f8d7fe166165f2e8d49371803b55 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 11:26:09 +0100 Subject: [PATCH 03/25] Get movies with screenings --- package.json | 3 ++- src/domains/movies.js | 6 +++++- src/routers/movies.js | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ebaf03be..3ad665ab 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "start": "npx nodemon ./src/index.js", "test:migration": "node test/testDbMigration.js", "test": "npx jest -i test/api/routes", - "test-extensions": "npx jest -i test/api/extensions --forceExit" + "test-extensions": "npx jest -i test/api/extensions --forceExit", + "test-movies": "npx jest -i test/api/routes/movies.spec.js" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/src/domains/movies.js b/src/domains/movies.js index 5feaad24..68bcd00e 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -1,7 +1,11 @@ const prisma = require("../utils/prisma"); async function getMoviesDb() { - const movies = await prisma.movie.findMany(); + const movies = await prisma.movie.findMany({ + include: { + screenings: true + } + }); return movies; } diff --git a/src/routers/movies.js b/src/routers/movies.js index 39044989..6872ab78 100644 --- a/src/routers/movies.js +++ b/src/routers/movies.js @@ -5,6 +5,7 @@ const { } = require('../controllers/movies'); router.get("/", getMovies) +// router.post("/", createMovie) module.exports = router; From 594891248285e0dbb7537b1656f407a295d8b9d2 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 11:40:13 +0100 Subject: [PATCH 04/25] Get movie by id --- src/controllers/movies.js | 19 ++++++++++++++++--- src/domains/movies.js | 30 +++++++++++++++++++++++++++--- src/routers/movies.js | 7 +++++-- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 3d017fa2..130e1638 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -1,15 +1,28 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") -const { getMoviesDb } = require('../domains/movies.js') +const { getMoviesDb, createMovieDb, getMovieByIdDb } = require('../domains/movies.js') async function getMovies(req, res) { - console.log('in') const movies = await getMoviesDb() res.status(200).json({ movies }) } + async function createMovie(req, res) { + const newMovie = req.body + const movie = await createMovieDb(newMovie) + res.status(201).json({ movie }) + } + + async function getMovieById(req, res) { + const id = Number(req.params.id) + const movie = await getMovieByIdDb(id) + res.status(200).json( { movie } ) + } + module.exports = { - getMovies + getMovies, + createMovie, + getMovieById } \ No newline at end of file diff --git a/src/domains/movies.js b/src/domains/movies.js index 68bcd00e..3b10a63f 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -3,12 +3,36 @@ const prisma = require("../utils/prisma"); async function getMoviesDb() { const movies = await prisma.movie.findMany({ include: { - screenings: true - } + screenings: true, + }, }); return movies; } +async function createMovieDb(newMovie) { + const movie = await prisma.movie.create({ + data: newMovie, + include: { + screenings: true, + }, + }); + return movie; +} + +async function getMovieByIdDb(movieId) { + const movie = await prisma.movie.findUnique({ + where: { + id: movieId + }, + include: { + screenings: true, + } + }) + return movie +} + module.exports = { - getMoviesDb + getMoviesDb, + createMovieDb, + getMovieByIdDb }; diff --git a/src/routers/movies.js b/src/routers/movies.js index 6872ab78..152c7fbe 100644 --- a/src/routers/movies.js +++ b/src/routers/movies.js @@ -1,11 +1,14 @@ const express = require("express"); const router = express.Router() const { - getMovies + getMovies, + createMovie, + getMovieById } = require('../controllers/movies'); router.get("/", getMovies) -// router.post("/", createMovie) +router.post("/", createMovie) +router.get("/:id", getMovieById) module.exports = router; From d3385222ec5565939d13b5057ecf1566c3c87b22 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 11:50:38 +0100 Subject: [PATCH 05/25] Update movie by id --- src/controllers/movies.js | 12 +++++++++--- src/domains/movies.js | 35 +++++++++++++++++++++++++---------- src/routers/movies.js | 4 +++- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 130e1638..13ec91f1 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -1,5 +1,5 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") -const { getMoviesDb, createMovieDb, getMovieByIdDb } = require('../domains/movies.js') +const { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb } = require('../domains/movies.js') async function getMovies(req, res) { @@ -19,10 +19,16 @@ async function getMovies(req, res) { res.status(200).json( { movie } ) } - +async function updateMovieById(req, res) { + const id = Number(req.params.id) + const updatedProps = req.body + const movie = await updateMovieByIdDb(id, updatedProps) + res.status(201).json( { movie } ) +} module.exports = { getMovies, createMovie, - getMovieById + getMovieById, + updateMovieById } \ No newline at end of file diff --git a/src/domains/movies.js b/src/domains/movies.js index 3b10a63f..461a070d 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -20,19 +20,34 @@ async function createMovieDb(newMovie) { } async function getMovieByIdDb(movieId) { - const movie = await prisma.movie.findUnique({ - where: { - id: movieId - }, - include: { - screenings: true, - } - }) - return movie + const movie = await prisma.movie.findUnique({ + where: { + id: movieId, + }, + include: { + screenings: true, + }, + }); + return movie; +} + +async function updateMovieByIdDb(movieId, updatedProps) { + console.log(updatedProps); + const movie = await prisma.movie.update({ + where: { + id: movieId, + }, + data: updatedProps, + include: { + screenings: true + } + }); + return movie; } module.exports = { getMoviesDb, createMovieDb, - getMovieByIdDb + getMovieByIdDb, + updateMovieByIdDb, }; diff --git a/src/routers/movies.js b/src/routers/movies.js index 152c7fbe..f941dc51 100644 --- a/src/routers/movies.js +++ b/src/routers/movies.js @@ -3,12 +3,14 @@ const router = express.Router() const { getMovies, createMovie, - getMovieById + getMovieById, + updateMovieById } = require('../controllers/movies'); router.get("/", getMovies) router.post("/", createMovie) router.get("/:id", getMovieById) +router.put("/:id", updateMovieById) module.exports = router; From 649d8b22ceb393e1f4199a4dac2e5bf3f2314480 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 12:01:00 +0100 Subject: [PATCH 06/25] Update customer by ID --- package.json | 3 ++- src/controllers/customer.js | 40 ++++++++++++++++++++++--------------- src/domains/customer.js | 15 +++++++++++++- src/routers/customer.js | 9 ++++----- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 3ad665ab..5aeb6c1b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test:migration": "node test/testDbMigration.js", "test": "npx jest -i test/api/routes", "test-extensions": "npx jest -i test/api/extensions --forceExit", - "test-movies": "npx jest -i test/api/routes/movies.spec.js" + "test-movies": "npx jest -i test/api/routes/movies.spec.js", + "test-customer": "npx jest -i test/api/routes/customer.spec.js" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/src/controllers/customer.js b/src/controllers/customer.js index dcfa7f21..f0a1ffaa 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -1,17 +1,16 @@ -const { PrismaClientKnownRequestError } = require("@prisma/client") -const { createCustomerDb } = require('../domains/customer.js') +const { PrismaClientKnownRequestError } = require("@prisma/client"); +const { + createCustomerDb, + updateCustomerByIdDb, +} = require("../domains/customer.js"); const createCustomer = async (req, res) => { - const { - name, - phone, - email - } = req.body + const { name, phone, email } = req.body; if (!name || !phone || !email) { return res.status(400).json({ - error: "Missing fields in request body" - }) + error: "Missing fields in request body", + }); } // Try-catch is a very common way to handle errors in JavaScript. @@ -22,9 +21,9 @@ const createCustomer = async (req, res) => { // instead of the Prisma error being thrown (and the app potentially crashing) we exit the // `try` block (bypassing the `res.status` code) and enter the `catch` block. try { - const createdCustomer = await createCustomerDb(name, phone, email) + const createdCustomer = await createCustomerDb(name, phone, email); - res.status(201).json({ customer: createdCustomer }) + res.status(201).json({ customer: createdCustomer }); } catch (e) { // In this catch block, we are able to specify how different Prisma errors are handled. // Prisma throws errors with its own codes. P2002 is the error code for @@ -35,15 +34,24 @@ const createCustomer = async (req, res) => { // HTTP error codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses if (e instanceof PrismaClientKnownRequestError) { if (e.code === "P2002") { - return res.status(409).json({ error: "A customer with the provided email already exists" }) + return res + .status(409) + .json({ error: "A customer with the provided email already exists" }); } } - res.status(500).json({ error: e.message }) + res.status(500).json({ error: e.message }); } -} +}; +async function updateCustomerById(req, res) { + const id = Number(req.params.id); + const newProps = req.body; + const customer = await updateCustomerByIdDb(id, newProps); + res.status(201).json({ customer }); +} module.exports = { - createCustomer -} + createCustomer, + updateCustomerById, +}; diff --git a/src/domains/customer.js b/src/domains/customer.js index 48b9fd00..a0e55191 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -21,7 +21,20 @@ const createCustomerDb = async (name, phone, email) => await prisma.customer.cre } }) +async function updateCustomerByIdDb(customerId, newProps) { + const customer = await prisma.customer.update({ + where: { + id: customerId + }, + data: newProps, + include: { + contact: true + } + }) + return customer +} module.exports = { - createCustomerDb + createCustomerDb, + updateCustomerByIdDb } diff --git a/src/routers/customer.js b/src/routers/customer.js index 2abc72ea..aa982040 100644 --- a/src/routers/customer.js +++ b/src/routers/customer.js @@ -1,15 +1,14 @@ const express = require("express"); const { - createCustomer, getMovies + createCustomer, + updateCustomerById } = require('../controllers/customer'); const router = express.Router(); -// In index.js, we told express that the /customer route should use this router file -// The below /register route extends that, so the end result will be a URL -// that looks like http://localhost:4040/customer/register -router.post("/register", createCustomer); +router.post("/register", createCustomer); +router.put("/:id", updateCustomerById) module.exports = router; From f405439bbed73baa54f196ac04be526e0d1ffb18 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 12:12:03 +0100 Subject: [PATCH 07/25] Create a screen --- src/controllers/screens.js | 9 +++++++++ src/domains/movies.js | 1 - src/domains/screens.js | 10 ++++++++++ src/routers/screens.js | 8 ++++++++ src/server.js | 2 ++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/controllers/screens.js create mode 100644 src/domains/screens.js create mode 100644 src/routers/screens.js diff --git a/src/controllers/screens.js b/src/controllers/screens.js new file mode 100644 index 00000000..25f6e8e6 --- /dev/null +++ b/src/controllers/screens.js @@ -0,0 +1,9 @@ +const { createScreenDb } = require('../domains/screens') + +async function createScreen(req, res) { + const props = req.body + const screen = await createScreenDb(props) + res.status(201).json({ screen }) +} + +module.exports = { createScreen } \ No newline at end of file diff --git a/src/domains/movies.js b/src/domains/movies.js index 461a070d..a7c7577a 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -32,7 +32,6 @@ async function getMovieByIdDb(movieId) { } async function updateMovieByIdDb(movieId, updatedProps) { - console.log(updatedProps); const movie = await prisma.movie.update({ where: { id: movieId, diff --git a/src/domains/screens.js b/src/domains/screens.js new file mode 100644 index 00000000..fad5d462 --- /dev/null +++ b/src/domains/screens.js @@ -0,0 +1,10 @@ +const prisma = require('../utils/prisma') + +async function createScreenDb(props) { + const screen = await prisma.screen.create({ + data: props + }) + return screen +} + +module.exports = { createScreenDb } \ No newline at end of file diff --git a/src/routers/screens.js b/src/routers/screens.js new file mode 100644 index 00000000..1a2ed088 --- /dev/null +++ b/src/routers/screens.js @@ -0,0 +1,8 @@ +const express = require("express"); +const router = express.Router(); +const { createScreen } = require('../controllers/screens') + +router.post('/', createScreen) + + +module.exports = router; diff --git a/src/server.js b/src/server.js index f90d2d5e..6c8d357a 100644 --- a/src/server.js +++ b/src/server.js @@ -16,8 +16,10 @@ app.use(express.urlencoded({ extended: true })); // Tell express to use your routers here const customerRouter = require('./routers/customer'); const movieRouter = require('./routers/movies') +const screenRouter = require('./routers/screens') app.use('/customers', customerRouter); app.use('/movies', movieRouter); +app.use('/screens', screenRouter); module.exports = app From 005bbc09f01850a55699a596bb2ca76adc4d73e0 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 13:27:47 +0100 Subject: [PATCH 08/25] Get movies with runtime limits --- package.json | 3 ++- src/controllers/movies.js | 9 +++++-- src/domains/movies.js | 17 +++++++++++++ test/api/extensions/movies-ext.spec.js | 35 ++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 test/api/extensions/movies-ext.spec.js diff --git a/package.json b/package.json index 5aeb6c1b..5c03a08f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "test": "npx jest -i test/api/routes", "test-extensions": "npx jest -i test/api/extensions --forceExit", "test-movies": "npx jest -i test/api/routes/movies.spec.js", - "test-customer": "npx jest -i test/api/routes/customer.spec.js" + "test-customer": "npx jest -i test/api/routes/customer.spec.js", + "test-movies-extensions": "npx jest -i test/api/extensions/movies-ext.spec.js --forceExit" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 13ec91f1..88629595 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -1,9 +1,14 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") -const { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb } = require('../domains/movies.js') +const { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb, getMoviesWithQueryDb } = require('../domains/movies.js') async function getMovies(req, res) { - const movies = await getMoviesDb() + let movies; + if(Object.keys(req.query).length > 0) { + movies = await getMoviesWithQueryDb(req.query) + } else { + movies = await getMoviesDb() + } res.status(200).json({ movies }) } diff --git a/src/domains/movies.js b/src/domains/movies.js index a7c7577a..59998930 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -44,9 +44,26 @@ async function updateMovieByIdDb(movieId, updatedProps) { return movie; } +async function getMoviesWithQueryDb(query) { + const { runtimeLt, runtimeGt } = query + + const runTimeLimits = { + ...(runtimeLt && { lt: Number(runtimeLt) }), + ...(runtimeGt && { gt: Number(runtimeGt) }) + }; + + const movies = await prisma.movie.findMany({ + where: { + runtimeMins: runTimeLimits + } + }) + return movies +} + module.exports = { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb, + getMoviesWithQueryDb }; diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js new file mode 100644 index 00000000..d8e9309b --- /dev/null +++ b/test/api/extensions/movies-ext.spec.js @@ -0,0 +1,35 @@ +const supertest = require("supertest"); +const app = require("../../../src/server.js"); + +const { createMovie } = require("../../helpers/createMovie.js"); +const { createScreen } = require("../../helpers/createScreen.js"); + +describe("Movies Endpoint", () => { + describe("GET /movies/runtimeLt", () => { + it("will return movies less than a certain runtime", async () => { + const screen = await createScreen(1); + await createMovie("Dodgeball", 120, screen); + await createMovie("Scream", 113, screen); + + const response = await supertest(app).get("/movies?runtimeLt=115"); + + expect(response.status).toEqual(200); + expect(response.body.movies.length).toEqual(1); + expect(response.body.movies[0].runtimeMins).toBeLessThan(115); + }); + }); + + describe("GET /movies/runtimeGt", () => { + it("will return movies over a certain runtime", async () => { + const screen = await createScreen(1); + await createMovie("The Exorcist", 150, screen); + await createMovie("Spaceballs", 140, screen); + + const response = await supertest(app).get("/movies?runtimeGt=145"); + + expect(response.status).toEqual(200); + expect(response.body.movies.length).toEqual(1); + expect(response.body.movies[0].runtimeMins).toBeGreaterThan(115); + }); + }); +}); From 862ece176b0b6ac5909a220cf53c067fbdf49c7e Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 13:51:50 +0100 Subject: [PATCH 09/25] Reject movie POST if missing fields --- package-lock.json | 9 +++++++++ package.json | 1 + src/controllers/movies.js | 7 +++++++ src/errors/errors.js | 5 +++++ src/server.js | 12 ++++++++++++ test/api/extensions/movies-ext.spec.js | 13 +++++++++++++ 6 files changed, 47 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0fc727e5..a9b8b9e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-errors": "^3.1.1", "morgan": "^1.10.0" }, "devDependencies": { @@ -2146,6 +2147,14 @@ "node": ">= 0.10.0" } }, + "node_modules/express-async-errors": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==", + "peerDependencies": { + "express": "^4.16.2" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", diff --git a/package.json b/package.json index 5c03a08f..6ad595cb 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-errors": "^3.1.1", "morgan": "^1.10.0" } } diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 88629595..cdc2774c 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -1,5 +1,6 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") const { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb, getMoviesWithQueryDb } = require('../domains/movies.js') +const { MissingFieldsError } = require("../errors/errors.js") async function getMovies(req, res) { @@ -14,6 +15,12 @@ async function getMovies(req, res) { async function createMovie(req, res) { const newMovie = req.body + + const requiredFields = ['title', 'runtimeMins'] + if (!requiredFields.every((field) => newMovie[field])) { + throw new MissingFieldsError('Movies require a title and runtime') + } + const movie = await createMovieDb(newMovie) res.status(201).json({ movie }) } diff --git a/src/errors/errors.js b/src/errors/errors.js index e69de29b..eaa38956 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -0,0 +1,5 @@ +class MissingFieldsError extends Error { + +} + +module.exports = { MissingFieldsError } \ No newline at end of file diff --git a/src/server.js b/src/server.js index 6c8d357a..a24797de 100644 --- a/src/server.js +++ b/src/server.js @@ -1,5 +1,6 @@ const express = require('express'); const app = express(); +require("express-async-errors") const cors = require('cors'); const morgan = require('morgan'); @@ -21,5 +22,16 @@ app.use('/customers', customerRouter); app.use('/movies', movieRouter); app.use('/screens', screenRouter); +const { MissingFieldsError } = require("./errors/errors.js") + + +//Errors +app.use((error, req, res, next) => { + if (error instanceof MissingFieldsError) { + return res.status(400).json({ + error: error.message + }) + } +}) module.exports = app diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index d8e9309b..a44bcd9b 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -32,4 +32,17 @@ describe("Movies Endpoint", () => { expect(response.body.movies[0].runtimeMins).toBeGreaterThan(115); }); }); + + describe("POST /movies/", () => { + it("will throw an error if fields missing from body", async () => { + const request = { + title: "Top Gun", + runtimeMins: null, + }; + const response = await supertest(app).post("/movies").send(request); + + expect(response.status).toEqual(400); + expect(response.body.error).toEqual("Movies require a title and runtime"); + }); + }); }); From 65150513a06ba88bb3d8329ccf6ccfe51f762bbc Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 15:24:40 +0100 Subject: [PATCH 10/25] Throw error if adding a movie with a title that already exists in db --- src/controllers/movies.js | 7 ++++++- src/errors/errors.js | 6 +++++- src/server.js | 8 +++++++- test/api/extensions/movies-ext.spec.js | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index cdc2774c..30514825 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -1,6 +1,6 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") const { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb, getMoviesWithQueryDb } = require('../domains/movies.js') -const { MissingFieldsError } = require("../errors/errors.js") +const { MissingFieldsError, DataAlreadyExistsError } = require("../errors/errors.js") async function getMovies(req, res) { @@ -15,6 +15,11 @@ async function getMovies(req, res) { async function createMovie(req, res) { const newMovie = req.body + const movies = await getMoviesDb() + + if(movies.find((movie) => movie.title === newMovie.title)) { + throw new DataAlreadyExistsError('A movie with that title already exists') + } const requiredFields = ['title', 'runtimeMins'] if (!requiredFields.every((field) => newMovie[field])) { diff --git a/src/errors/errors.js b/src/errors/errors.js index eaa38956..6e7e5846 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -2,4 +2,8 @@ class MissingFieldsError extends Error { } -module.exports = { MissingFieldsError } \ No newline at end of file +class DataAlreadyExistsError extends Error { + +} + +module.exports = { MissingFieldsError, DataAlreadyExistsError } \ No newline at end of file diff --git a/src/server.js b/src/server.js index a24797de..84343700 100644 --- a/src/server.js +++ b/src/server.js @@ -22,7 +22,7 @@ app.use('/customers', customerRouter); app.use('/movies', movieRouter); app.use('/screens', screenRouter); -const { MissingFieldsError } = require("./errors/errors.js") +const { MissingFieldsError, DataAlreadyExistsError } = require("./errors/errors.js") //Errors @@ -32,6 +32,12 @@ app.use((error, req, res, next) => { error: error.message }) } + + if (error instanceof DataAlreadyExistsError) { + return res.status(409).json({ + error: error.message + }) + } }) module.exports = app diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index a44bcd9b..03a8242f 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -44,5 +44,21 @@ describe("Movies Endpoint", () => { expect(response.status).toEqual(400); expect(response.body.error).toEqual("Movies require a title and runtime"); }); + + it("will throw an error if a movie already exists with that name", async () => { + const screen = await createScreen(1); + await createMovie("The Exorcist", 150, screen); + await createMovie("Spaceballs", 140, screen); + + const request = { + title: "Spaceballs", + runtimeMins: 140, + }; + + const response = await supertest(app).post("/movies").send(request); + + expect(response.status).toEqual(409) + expect(response.body.error).toEqual("A movie with that title already exists"); + }); }); }); From 23ede7fe90bb9e764db36206363c14a9828ae477 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 15:36:45 +0100 Subject: [PATCH 11/25] Throw error if getting movie ID fails because no movie exists --- src/controllers/movies.js | 90 +++++++++++++++----------- src/errors/errors.js | 6 +- src/server.js | 8 ++- test/api/extensions/movies-ext.spec.js | 33 ++++++---- 4 files changed, 86 insertions(+), 51 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 30514825..98922cab 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -1,51 +1,65 @@ -const { PrismaClientKnownRequestError } = require("@prisma/client") -const { getMoviesDb, createMovieDb, getMovieByIdDb, updateMovieByIdDb, getMoviesWithQueryDb } = require('../domains/movies.js') -const { MissingFieldsError, DataAlreadyExistsError } = require("../errors/errors.js") - +const { PrismaClientKnownRequestError } = require("@prisma/client"); +const { + getMoviesDb, + createMovieDb, + getMovieByIdDb, + updateMovieByIdDb, + getMoviesWithQueryDb, +} = require("../domains/movies.js"); +const { + MissingFieldsError, + DataAlreadyExistsError, + DataNotFoundError +} = require("../errors/errors.js"); async function getMovies(req, res) { - let movies; - if(Object.keys(req.query).length > 0) { - movies = await getMoviesWithQueryDb(req.query) - } else { - movies = await getMoviesDb() - } - res.status(200).json({ movies }) + let movies; + if (Object.keys(req.query).length > 0) { + movies = await getMoviesWithQueryDb(req.query); + } else { + movies = await getMoviesDb(); } + res.status(200).json({ movies }); +} - async function createMovie(req, res) { - const newMovie = req.body - const movies = await getMoviesDb() - - if(movies.find((movie) => movie.title === newMovie.title)) { - throw new DataAlreadyExistsError('A movie with that title already exists') - } +async function createMovie(req, res) { + const newMovie = req.body; + const movies = await getMoviesDb(); - const requiredFields = ['title', 'runtimeMins'] - if (!requiredFields.every((field) => newMovie[field])) { - throw new MissingFieldsError('Movies require a title and runtime') - } + if (movies.find((movie) => movie.title === newMovie.title)) { + throw new DataAlreadyExistsError("A movie with that title already exists"); + } - const movie = await createMovieDb(newMovie) - res.status(201).json({ movie }) + const requiredFields = ["title", "runtimeMins"]; + if (!requiredFields.every((field) => newMovie[field])) { + throw new MissingFieldsError("Movies require a title and runtime"); } - async function getMovieById(req, res) { - const id = Number(req.params.id) - const movie = await getMovieByIdDb(id) - res.status(200).json( { movie } ) + const movie = await createMovieDb(newMovie); + res.status(201).json({ movie }); +} + +async function getMovieById(req, res) { + const id = Number(req.params.id); + const movie = await getMovieByIdDb(id); + + if (!movie) { + throw new DataNotFoundError('No movie found with that ID') } + + res.status(200).json({ movie }); +} async function updateMovieById(req, res) { - const id = Number(req.params.id) - const updatedProps = req.body - const movie = await updateMovieByIdDb(id, updatedProps) - res.status(201).json( { movie } ) + const id = Number(req.params.id); + const updatedProps = req.body; + const movie = await updateMovieByIdDb(id, updatedProps); + res.status(201).json({ movie }); } - module.exports = { - getMovies, - createMovie, - getMovieById, - updateMovieById - } \ No newline at end of file +module.exports = { + getMovies, + createMovie, + getMovieById, + updateMovieById, +}; diff --git a/src/errors/errors.js b/src/errors/errors.js index 6e7e5846..0ec6714a 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -6,4 +6,8 @@ class DataAlreadyExistsError extends Error { } -module.exports = { MissingFieldsError, DataAlreadyExistsError } \ No newline at end of file +class DataNotFoundError extends Error { + +} + +module.exports = { MissingFieldsError, DataAlreadyExistsError, DataNotFoundError } \ No newline at end of file diff --git a/src/server.js b/src/server.js index 84343700..d380af3e 100644 --- a/src/server.js +++ b/src/server.js @@ -22,7 +22,7 @@ app.use('/customers', customerRouter); app.use('/movies', movieRouter); app.use('/screens', screenRouter); -const { MissingFieldsError, DataAlreadyExistsError } = require("./errors/errors.js") +const { MissingFieldsError, DataAlreadyExistsError, DataNotFoundError } = require("./errors/errors.js") //Errors @@ -38,6 +38,12 @@ app.use((error, req, res, next) => { error: error.message }) } + + if (error instanceof DataNotFoundError) { + return res.status(404).json({ + error: error.message + }) + } }) module.exports = app diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index 03a8242f..f02a3bb8 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -31,6 +31,15 @@ describe("Movies Endpoint", () => { expect(response.body.movies.length).toEqual(1); expect(response.body.movies[0].runtimeMins).toBeGreaterThan(115); }); + + describe("GET /movies/:id", () => { + it("will throw an error if no movie exists with given id", async () => { + const response = await supertest(app).get("/movies/30"); + + expect(response.status).toEqual(404); + expect(response.body.error).toEqual('No movie found with that ID'); + }); + }); }); describe("POST /movies/", () => { @@ -46,19 +55,21 @@ describe("Movies Endpoint", () => { }); it("will throw an error if a movie already exists with that name", async () => { - const screen = await createScreen(1); - await createMovie("The Exorcist", 150, screen); - await createMovie("Spaceballs", 140, screen); + const screen = await createScreen(1); + await createMovie("The Exorcist", 150, screen); + await createMovie("Spaceballs", 140, screen); - const request = { - title: "Spaceballs", - runtimeMins: 140, - }; + const request = { + title: "Spaceballs", + runtimeMins: 140, + }; - const response = await supertest(app).post("/movies").send(request); + const response = await supertest(app).post("/movies").send(request); - expect(response.status).toEqual(409) - expect(response.body.error).toEqual("A movie with that title already exists"); - }); + expect(response.status).toEqual(409); + expect(response.body.error).toEqual( + "A movie with that title already exists" + ); + }); }); }); From de3acaf1caab921f8af19e697434f4f40c415261 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 17:36:16 +0100 Subject: [PATCH 12/25] Throw errors when putting a movie with existing title, missing fields, or no film with id --- src/controllers/movies.js | 18 +++++++++- test/api/extensions/movies-ext.spec.js | 49 +++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 98922cab..68f4c6fa 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -46,13 +46,29 @@ async function getMovieById(req, res) { if (!movie) { throw new DataNotFoundError('No movie found with that ID') } - + res.status(200).json({ movie }); } async function updateMovieById(req, res) { const id = Number(req.params.id); const updatedProps = req.body; + const foundMovie = await getMovieByIdDb(id); + + const movies = await getMoviesDb(); + + if (movies.find((movie) => updatedProps.title === movie.title)) { + throw new DataAlreadyExistsError("A movie with that title already exists"); + } + + if (!foundMovie) { + throw new DataNotFoundError('No movie found with that ID') + } + + if (!updatedProps.title && !updatedProps.runtimeMins) { + throw new MissingFieldsError("Updating movies requires a title or runtime"); + } + const movie = await updateMovieByIdDb(id, updatedProps); res.status(201).json({ movie }); } diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index f02a3bb8..fe5967d1 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -37,7 +37,7 @@ describe("Movies Endpoint", () => { const response = await supertest(app).get("/movies/30"); expect(response.status).toEqual(404); - expect(response.body.error).toEqual('No movie found with that ID'); + expect(response.body.error).toEqual("No movie found with that ID"); }); }); }); @@ -71,5 +71,52 @@ describe("Movies Endpoint", () => { "A movie with that title already exists" ); }); + + describe("PUT /movies/:id", () => { + it("will throw an error if no film exists with given ID", async () => { + const request = { + title: "Top Gun", + runtimeMins: 146, + }; + + const response = await supertest(app).put("/movies/30").send(request); + + expect(response.status).toEqual(404); + expect(response.body.error).toEqual("No movie found with that ID"); + }); + + it("will throw an error if film body is missing fields", async () => { + const screen = await createScreen(1); + const created = await createMovie("Dodgeball", 120, screen); + + const request = { + cheese: "cheddar" + }; + + const response = await supertest(app).put(`/movies/${created.id}`).send(request); + + expect(response.status).toEqual(400); + expect(response.body.error).toEqual( + "Updating movies requires a title or runtime" + ); + }); + + it("will throw an error if a movie already exists with that name", async () => { + const screen = await createScreen(1); + const created = await createMovie("Dodgeball", 120, screen); + const created2 = await createMovie("Signs", 109, screen) + + const request = { + title: "Dodgeball" + }; + + const response = await supertest(app).put(`/movies/${created2.id}`).send(request); + + expect(response.status).toEqual(409); + expect(response.body.error).toEqual( + "A movie with that title already exists" + ); + }); + }); }); }); From b4c77a67313df1fcf0e16ebfaa0ecfa089130a47 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Mon, 1 Jul 2024 20:09:15 +0100 Subject: [PATCH 13/25] Update customer and contact simultaneously --- package.json | 3 ++- src/controllers/customer.js | 21 ++++++--------------- src/domains/customer.js | 16 +++++++++------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 6ad595cb..00277654 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "test-extensions": "npx jest -i test/api/extensions --forceExit", "test-movies": "npx jest -i test/api/routes/movies.spec.js", "test-customer": "npx jest -i test/api/routes/customer.spec.js", - "test-movies-extensions": "npx jest -i test/api/extensions/movies-ext.spec.js --forceExit" + "test-movies-extensions": "npx jest -i test/api/extensions/movies-ext.spec.js --forceExit", + "test-customer-extensions": "npx jest -i test/api/extensions/customer-ext.spec.js --forceExit" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/src/controllers/customer.js b/src/controllers/customer.js index f0a1ffaa..fd2b0287 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -13,25 +13,10 @@ const createCustomer = async (req, res) => { }); } - // Try-catch is a very common way to handle errors in JavaScript. - // It allows us to customise how we want errors that are thrown to be handled. - // Read more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch - - // Here, if Prisma throws an error in the process of trying to create a new customer, - // instead of the Prisma error being thrown (and the app potentially crashing) we exit the - // `try` block (bypassing the `res.status` code) and enter the `catch` block. try { const createdCustomer = await createCustomerDb(name, phone, email); - res.status(201).json({ customer: createdCustomer }); } catch (e) { - // In this catch block, we are able to specify how different Prisma errors are handled. - // Prisma throws errors with its own codes. P2002 is the error code for - // "Unique constraint failed on the {constraint}". In our case, the {constraint} is the - // email field which we have set as needing to be unique in the prisma.schema. - // To handle this, we return a custom 409 (conflict) error as a response to the client. - // Prisma error codes: https://www.prisma.io/docs/orm/reference/error-reference#common - // HTTP error codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses if (e instanceof PrismaClientKnownRequestError) { if (e.code === "P2002") { return res @@ -45,6 +30,7 @@ const createCustomer = async (req, res) => { }; async function updateCustomerById(req, res) { + console.log('in') const id = Number(req.params.id); const newProps = req.body; const customer = await updateCustomerByIdDb(id, newProps); @@ -55,3 +41,8 @@ module.exports = { createCustomer, updateCustomerById, }; + + + + // Prisma error codes: https://www.prisma.io/docs/orm/reference/error-reference#common + // HTTP error codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses \ No newline at end of file diff --git a/src/domains/customer.js b/src/domains/customer.js index a0e55191..54c300df 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -1,9 +1,6 @@ const prisma = require('../utils/prisma') -/** - * This will create a Customer AND create a new Contact, then automatically relate them with each other - * @tutorial https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#create-a-related-record - */ + const createCustomerDb = async (name, phone, email) => await prisma.customer.create({ data: { name, @@ -14,19 +11,22 @@ const createCustomerDb = async (name, phone, email) => await prisma.customer.cre } } }, - // We add an `include` outside of the `data` object to make sure the new contact is returned in the result - // This is like doing RETURNING in SQL + include: { contact: true } }) async function updateCustomerByIdDb(customerId, newProps) { + const customer = await prisma.customer.update({ where: { id: customerId }, - data: newProps, + data: { ...newProps, + contact: newProps.contact ? { + update: newProps.contact } : undefined, + }, include: { contact: true } @@ -34,6 +34,8 @@ async function updateCustomerByIdDb(customerId, newProps) { return customer } + + module.exports = { createCustomerDb, updateCustomerByIdDb From e6a0ec2240ae4f149854782749e0a0035920b7ae Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 09:24:39 +0100 Subject: [PATCH 14/25] Throw error when updating a non-existant customer --- src/controllers/customer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controllers/customer.js b/src/controllers/customer.js index fd2b0287..9d7d4787 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -30,11 +30,16 @@ const createCustomer = async (req, res) => { }; async function updateCustomerById(req, res) { - console.log('in') const id = Number(req.params.id); const newProps = req.body; + try { const customer = await updateCustomerByIdDb(id, newProps); res.status(201).json({ customer }); + } catch (e) { + if (e.code === 'P2025') { + return res.status(404).json({ error: e.message }) + } + } } module.exports = { From 5dea551eb8d7d1e75ff2e9311b9c6c73aa4682f5 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 09:44:58 +0100 Subject: [PATCH 15/25] Throw error when updating customer without a name --- src/controllers/customer.js | 9 +++++++++ src/errors/errors.js | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/controllers/customer.js b/src/controllers/customer.js index 9d7d4787..a62b2f37 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -3,6 +3,7 @@ const { createCustomerDb, updateCustomerByIdDb, } = require("../domains/customer.js"); +const { DataNotFoundError, MissingFieldsError } = require("../errors/errors.js"); const createCustomer = async (req, res) => { const { name, phone, email } = req.body; @@ -31,7 +32,15 @@ const createCustomer = async (req, res) => { async function updateCustomerById(req, res) { const id = Number(req.params.id); + if (isNaN(id)) { + throw new DataNotFoundError('No customer found with that ID') + } + const newProps = req.body; + if (!newProps.name) { + throw new MissingFieldsError('Customers require a name') + } + try { const customer = await updateCustomerByIdDb(id, newProps); res.status(201).json({ customer }); diff --git a/src/errors/errors.js b/src/errors/errors.js index 0ec6714a..d2e2eb0c 100644 --- a/src/errors/errors.js +++ b/src/errors/errors.js @@ -10,4 +10,6 @@ class DataNotFoundError extends Error { } + + module.exports = { MissingFieldsError, DataAlreadyExistsError, DataNotFoundError } \ No newline at end of file From 64801d8ab3a1ec15619691f51e424dfbd9b68afc Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 10:47:13 +0100 Subject: [PATCH 16/25] Updated Screens model to make numbers unique, added error handling for creating screen without a number and with a number that already exists --- package.json | 3 +- .../migration.sql | 8 +++++ prisma/schema.prisma | 4 +-- src/controllers/screens.js | 16 ++++++++-- test/api/extensions/screens-ext.spec.js | 29 +++++++++++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 prisma/migrations/20240702092124_added_unique_contraint_screen_number/migration.sql create mode 100644 test/api/extensions/screens-ext.spec.js diff --git a/package.json b/package.json index 00277654..cf89e7a9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test-movies": "npx jest -i test/api/routes/movies.spec.js", "test-customer": "npx jest -i test/api/routes/customer.spec.js", "test-movies-extensions": "npx jest -i test/api/extensions/movies-ext.spec.js --forceExit", - "test-customer-extensions": "npx jest -i test/api/extensions/customer-ext.spec.js --forceExit" + "test-customer-extensions": "npx jest -i test/api/extensions/customer-ext.spec.js --forceExit", + "test-screens-extensions": "npx jest -i test/api/extensions/screens-ext.spec.js --forceExit" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/prisma/migrations/20240702092124_added_unique_contraint_screen_number/migration.sql b/prisma/migrations/20240702092124_added_unique_contraint_screen_number/migration.sql new file mode 100644 index 00000000..e6b1f2d7 --- /dev/null +++ b/prisma/migrations/20240702092124_added_unique_contraint_screen_number/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[number]` on the table `Screen` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Screen_number_key" ON "Screen"("number"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dd9b27f1..892ba413 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -40,8 +40,8 @@ model Movie { } model Screen { - id Int @id @default(autoincrement()) - number Int + id Int @id @default(autoincrement()) + number Int @unique screenings Screening[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/controllers/screens.js b/src/controllers/screens.js index 25f6e8e6..2ce620f9 100644 --- a/src/controllers/screens.js +++ b/src/controllers/screens.js @@ -1,9 +1,21 @@ const { createScreenDb } = require('../domains/screens') +const { MissingFieldsError, DataAlreadyExistsError } = require('../errors/errors') async function createScreen(req, res) { const props = req.body - const screen = await createScreenDb(props) - res.status(201).json({ screen }) + + if (!props.number) { + throw new MissingFieldsError('All screens require a screen number') + } + + try { + const screen = await createScreenDb(props) + res.status(201).json({ screen }) + } catch (e) { + if (e.code === 'P2002') { + throw new DataAlreadyExistsError('There is already a screen with that number') + } + } } module.exports = { createScreen } \ No newline at end of file diff --git a/test/api/extensions/screens-ext.spec.js b/test/api/extensions/screens-ext.spec.js new file mode 100644 index 00000000..80c50f3e --- /dev/null +++ b/test/api/extensions/screens-ext.spec.js @@ -0,0 +1,29 @@ +const supertest = require("supertest"); +const app = require("../../../src/server.js"); +const { createScreen } = require("../../helpers/createScreen.js"); + +describe("Screens Endpoint", () => { + describe("POST /screens", () => { + it('should reject post requests where body does not contain screen number', async () => { + const request = {capacity: 41} + + const response = await supertest(app).post(`/screens/`).send(request); + + expect(response.status).toEqual(400) + expect(response.body.error).toEqual('All screens require a screen number') + }) + + it('should reject post requests where given screen number already exists', async () => { + const screen = await createScreen(1); + console.log(screen) + const request = {number: screen.number} + + const response = await supertest(app).post("/screens").send(request); + + expect(response.status).toEqual(409) + expect(response.body.error).toEqual('There is already a screen with that number') + + }) + + }) +}) \ No newline at end of file From 9c5557f69cf2966ff22a7fe04ebd0a1d7ebe5051 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 12:00:14 +0100 Subject: [PATCH 17/25] Helpers and routes for tickets --- src/controllers/tickets.js | 12 ++++++++++++ src/domains/tickets.js | 7 +++++++ src/routers/tickets.js | 8 ++++++++ src/server.js | 2 ++ test/api/extensions/tickets-ext.spec.js | 24 ++++++++++++++++++++++++ test/helpers/createScreening.js | 21 +++++++++++++++++++++ test/helpers/createTicket.js | 22 ++++++++++++++++++++++ 7 files changed, 96 insertions(+) create mode 100644 src/controllers/tickets.js create mode 100644 src/domains/tickets.js create mode 100644 src/routers/tickets.js create mode 100644 test/api/extensions/tickets-ext.spec.js create mode 100644 test/helpers/createScreening.js create mode 100644 test/helpers/createTicket.js diff --git a/src/controllers/tickets.js b/src/controllers/tickets.js new file mode 100644 index 00000000..9b8b51f9 --- /dev/null +++ b/src/controllers/tickets.js @@ -0,0 +1,12 @@ +const { createTicketDb } = require('../domains/tickets') +const { MissingFieldsError } = require('../errors/errors') + +function createTicket(req, res) { + const ticket = req.body + + if (!ticket.screeningId || !ticket.customerId) { + throw new MissingFieldsError('Tickets require a screening ID and a customer ID') + } +} + +module.exports = { createTicket } \ No newline at end of file diff --git a/src/domains/tickets.js b/src/domains/tickets.js new file mode 100644 index 00000000..d942210e --- /dev/null +++ b/src/domains/tickets.js @@ -0,0 +1,7 @@ +const prisma = require('../utils/prisma') + +function createTicketDb() { + +} + +module.exports = { createTicketDb } \ No newline at end of file diff --git a/src/routers/tickets.js b/src/routers/tickets.js new file mode 100644 index 00000000..648c1b54 --- /dev/null +++ b/src/routers/tickets.js @@ -0,0 +1,8 @@ +const express = require("express"); +const router = express.Router(); +const { createTicket } = require('../controllers/tickets') + +router.post('/', createTicket) + + +module.exports = router; \ No newline at end of file diff --git a/src/server.js b/src/server.js index d380af3e..f29c25e9 100644 --- a/src/server.js +++ b/src/server.js @@ -18,9 +18,11 @@ app.use(express.urlencoded({ extended: true })); const customerRouter = require('./routers/customer'); const movieRouter = require('./routers/movies') const screenRouter = require('./routers/screens') +const ticketsRouter = require('./routers/tickets.js') app.use('/customers', customerRouter); app.use('/movies', movieRouter); app.use('/screens', screenRouter); +app.use('/tickets', ticketsRouter); const { MissingFieldsError, DataAlreadyExistsError, DataNotFoundError } = require("./errors/errors.js") diff --git a/test/api/extensions/tickets-ext.spec.js b/test/api/extensions/tickets-ext.spec.js new file mode 100644 index 00000000..e9711f4c --- /dev/null +++ b/test/api/extensions/tickets-ext.spec.js @@ -0,0 +1,24 @@ +const supertest = require("supertest"); +const app = require("../../../src/server.js"); +const { createTicket } = require("../../helpers/createTicket.js"); +const { createCustomer } = require("../../helpers/createCustomer.js"); +const { createScreening } = require("../../helpers/createScreening.js"); +const { createScreen } = require("../../helpers/createScreen.js"); +const { createMovie } = require("../../helpers/createMovie.js"); + +describe("Tickets Endpoint", () => { + describe("POST /tickets", () => { + it("should throw an error when creating a ticket without a screening or customer ID", async () => { + const request = {}; + + const response = await supertest(app).post(`/tickets`).send(request); + }); + + it("should create new tickets", async () => { + const customer = createCustomer("Will Baxter", "999", "will@baxter.com"); + const screen = createScreen(5); + const movie = createMovie("The Legend of Bagger Vance", 120); + const screening = createScreening(movie, screen, new Date()); + }); + }); +}); diff --git a/test/helpers/createScreening.js b/test/helpers/createScreening.js new file mode 100644 index 00000000..cf3e277a --- /dev/null +++ b/test/helpers/createScreening.js @@ -0,0 +1,21 @@ +const prisma = require("../../src/utils/prisma"); + +async function createScreening(movie, screen, startTime) { + const screeningData = { + movieId: movie.id, + screenId: screen.id, + startsAt: startTime, + include: { + screen: true, + movie: true, + }, + }; + + const screening = await prisma.screening.create({ + data: screeningData, + }); +} + +module.exports = { + createScreening, +}; diff --git a/test/helpers/createTicket.js b/test/helpers/createTicket.js new file mode 100644 index 00000000..cfa8d453 --- /dev/null +++ b/test/helpers/createTicket.js @@ -0,0 +1,22 @@ +const prisma = require("../../src/utils/prisma"); + +async function createTicket(screening, customer) { + const ticketData = { + screeningId: screening.id, + customerId: customer.id, + include: { + screening: true, + customer: true, + screen: true, + movie: true, + }, + }; + + const ticket = await prisma.tickets.create({ + data: ticketData, + }); +} + +module.exports = { + createTicket, +}; From d02ef6b4f7288c4492defe89eb831c3aed4cf482 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 15:05:43 +0100 Subject: [PATCH 18/25] Create tickets --- package.json | 3 +- src/controllers/tickets.js | 50 +++++++++++++++++++++---- src/domains/tickets.js | 35 +++++++++++++++-- test/api/extensions/tickets-ext.spec.js | 30 +++++++++++++-- test/helpers/createScreening.js | 6 +-- 5 files changed, 104 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index cf89e7a9..be926fb7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "test-customer": "npx jest -i test/api/routes/customer.spec.js", "test-movies-extensions": "npx jest -i test/api/extensions/movies-ext.spec.js --forceExit", "test-customer-extensions": "npx jest -i test/api/extensions/customer-ext.spec.js --forceExit", - "test-screens-extensions": "npx jest -i test/api/extensions/screens-ext.spec.js --forceExit" + "test-screens-extensions": "npx jest -i test/api/extensions/screens-ext.spec.js --forceExit", + "test-tickets-extensions": "npx jest -i test/api/extensions/tickets-ext.spec.js --forceExit" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/src/controllers/tickets.js b/src/controllers/tickets.js index 9b8b51f9..47b05a77 100644 --- a/src/controllers/tickets.js +++ b/src/controllers/tickets.js @@ -1,12 +1,46 @@ -const { createTicketDb } = require('../domains/tickets') -const { MissingFieldsError } = require('../errors/errors') +const { createTicketDb } = require("../domains/tickets"); +const { MissingFieldsError } = require("../errors/errors"); -function createTicket(req, res) { - const ticket = req.body +async function createTicket(req, res) { + const ticketData = req.body; - if (!ticket.screeningId || !ticket.customerId) { - throw new MissingFieldsError('Tickets require a screening ID and a customer ID') - } + if (!ticketData.screeningId || !ticketData.customerId) { + throw new MissingFieldsError( + "Tickets require a screening ID and a customer ID" + ); + } + const ticket = await createTicketDb(ticketData); + + const result = { + id: ticket.id, + screening: { + id: ticket.screening.id, + movieId: ticket.screening.movieId, + startsAt: ticket.screening.startsAt, + createdAt: ticket.screening.createdAt, + updatedAt: ticket.screening.updatedAt, + }, + customer: { + id: ticket.customer.id, + name: ticket.customer.name, + createdAt: ticket.customer.createdAt, + updatedAt: ticket.customer.updatedAt, + }, + screen: { + id: ticket.screening.screen.id, + number: ticket.screening.screen.number, + createdAt: ticket.screening.screen.createdAt, + updatedAt: ticket.screening.screen.updatedAt, + }, + movie: { + id: ticket.screening.movie.id, + runtimeMins: ticket.screening.movie.runtimeMins, + createdAt: ticket.screening.movie.createdAt, + updatedAt: ticket.screening.movie.updatedAt, + }, + }; + + res.status(201).json({ ticket: result }); } -module.exports = { createTicket } \ No newline at end of file +module.exports = { createTicket }; diff --git a/src/domains/tickets.js b/src/domains/tickets.js index d942210e..8e88e9e3 100644 --- a/src/domains/tickets.js +++ b/src/domains/tickets.js @@ -1,7 +1,36 @@ -const prisma = require('../utils/prisma') +const prisma = require("../utils/prisma"); -function createTicketDb() { +async function createTicketDb(ticketData) { + const { screeningId, customerId } = ticketData; + const request = { + data: { + screeningId: Number(screeningId), + customerId: Number(customerId), + }, + include: { + screening: { + include: { + screen: true, + movie: true, + }, + }, + customer: { + include: { + contact: true, + }, + }, + }, + }; + + let ticket; + try { + ticket = await prisma.ticket.create(request); + } catch (e) { + console.log(e); + } + + return ticket; } -module.exports = { createTicketDb } \ No newline at end of file +module.exports = { createTicketDb }; diff --git a/test/api/extensions/tickets-ext.spec.js b/test/api/extensions/tickets-ext.spec.js index e9711f4c..ca305a8f 100644 --- a/test/api/extensions/tickets-ext.spec.js +++ b/test/api/extensions/tickets-ext.spec.js @@ -12,13 +12,35 @@ describe("Tickets Endpoint", () => { const request = {}; const response = await supertest(app).post(`/tickets`).send(request); + expect(response.status).toEqual(400); + expect(response.body.error).toEqual( + "Tickets require a screening ID and a customer ID" + ); }); it("should create new tickets", async () => { - const customer = createCustomer("Will Baxter", "999", "will@baxter.com"); - const screen = createScreen(5); - const movie = createMovie("The Legend of Bagger Vance", 120); - const screening = createScreening(movie, screen, new Date()); + const date = new Date(); + const customer = await createCustomer( + "Will Baxter", + "999", + "will@baxter.com" + ); + const screen = await createScreen(5); + const movie = await createMovie("The Legend of Bagger Vance", 120); + const screening = await createScreening(movie, screen, date); + + request = { + screeningId: screening.id, + customerId: customer.id, + }; + + const response = await supertest(app).post(`/tickets`).send(request); + + expect(response.status).toEqual(201) + expect(response.body.ticket.id).not.toBe(undefined) + expect(response.body.ticket.customer).not.toBe(undefined) + expect(response.body.ticket.screen).not.toBe(undefined) + expect(response.body.ticket.movie).not.toBe(undefined) }); }); }); diff --git a/test/helpers/createScreening.js b/test/helpers/createScreening.js index cf3e277a..25553856 100644 --- a/test/helpers/createScreening.js +++ b/test/helpers/createScreening.js @@ -5,15 +5,13 @@ async function createScreening(movie, screen, startTime) { movieId: movie.id, screenId: screen.id, startsAt: startTime, - include: { - screen: true, - movie: true, - }, }; const screening = await prisma.screening.create({ data: screeningData, }); + + return screening } module.exports = { From d049d98da28bfe8eda88396c3ed435522e83bfeb Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 15:58:37 +0100 Subject: [PATCH 19/25] Reject ticket creation if wrong data type, no IDs found, or no IDs provided --- src/controllers/tickets.js | 35 ++++++++++++++++++++++++- test/api/extensions/tickets-ext.spec.js | 34 ++++++++++++++++++++---- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/controllers/tickets.js b/src/controllers/tickets.js index 47b05a77..c712848d 100644 --- a/src/controllers/tickets.js +++ b/src/controllers/tickets.js @@ -1,5 +1,6 @@ const { createTicketDb } = require("../domains/tickets"); -const { MissingFieldsError } = require("../errors/errors"); +const { MissingFieldsError, DataNotFoundError } = require("../errors/errors"); +const prisma = require("../utils/prisma"); async function createTicket(req, res) { const ticketData = req.body; @@ -9,6 +10,38 @@ async function createTicket(req, res) { "Tickets require a screening ID and a customer ID" ); } + + if ( + isNaN(Number(ticketData.screeningId)) || + isNaN(Number(ticketData.customerId)) + ) { + throw new DataNotFoundError("No data found for screening or customer ID"); + } + + [screening, customer] = await prisma.$transaction([ + prisma.screening.findUnique({ + where: { + id: Number(ticketData.screeningId), + }, + }), + prisma.customer.findUnique({ + where: { + id: Number(ticketData.customerId), + }, + }), + ]); + + if (!screening || !customer) { + let errorString = "No data found for: "; + if (!screening) { + errorString += "screening "; + } + if (!customer) { + errorString += "customer"; + } + throw new DataNotFoundError(errorString); + } + const ticket = await createTicketDb(ticketData); const result = { diff --git a/test/api/extensions/tickets-ext.spec.js b/test/api/extensions/tickets-ext.spec.js index ca305a8f..ead1337b 100644 --- a/test/api/extensions/tickets-ext.spec.js +++ b/test/api/extensions/tickets-ext.spec.js @@ -36,11 +36,35 @@ describe("Tickets Endpoint", () => { const response = await supertest(app).post(`/tickets`).send(request); - expect(response.status).toEqual(201) - expect(response.body.ticket.id).not.toBe(undefined) - expect(response.body.ticket.customer).not.toBe(undefined) - expect(response.body.ticket.screen).not.toBe(undefined) - expect(response.body.ticket.movie).not.toBe(undefined) + expect(response.status).toEqual(201); + expect(response.body.ticket.id).not.toBe(undefined); + expect(response.body.ticket.customer).not.toBe(undefined); + expect(response.body.ticket.screen).not.toBe(undefined); + expect(response.body.ticket.movie).not.toBe(undefined); + }); + + it("should throw an error if customerId or screeningId do not exist", async () => { + request = { + screeningId: 999, + customerId: 999, + }; + + const response = await supertest(app).post(`/tickets`).send(request); + expect(response.body.error).toEqual( + "No data found for: screening customer" + ); + }); + + it("should throw an error if screen ID or customer ID are the wrong data type", async () => { + request = { + screeningId: "cheese", + customerId: 999, + }; + + const response = await supertest(app).post(`/tickets`).send(request); + expect(response.body.error).toEqual( + "No data found for screening or customer ID" + ); }); }); }); From 844fc4d3485006ce95ef887ac04d9bf6eedce369 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 16:49:17 +0100 Subject: [PATCH 20/25] Revert "Reject ticket creation if wrong data type, no IDs found, or no IDs provided" This reverts commit d049d98da28bfe8eda88396c3ed435522e83bfeb. --- src/controllers/tickets.js | 35 +------------------------ test/api/extensions/tickets-ext.spec.js | 34 ++++-------------------- 2 files changed, 6 insertions(+), 63 deletions(-) diff --git a/src/controllers/tickets.js b/src/controllers/tickets.js index c712848d..47b05a77 100644 --- a/src/controllers/tickets.js +++ b/src/controllers/tickets.js @@ -1,6 +1,5 @@ const { createTicketDb } = require("../domains/tickets"); -const { MissingFieldsError, DataNotFoundError } = require("../errors/errors"); -const prisma = require("../utils/prisma"); +const { MissingFieldsError } = require("../errors/errors"); async function createTicket(req, res) { const ticketData = req.body; @@ -10,38 +9,6 @@ async function createTicket(req, res) { "Tickets require a screening ID and a customer ID" ); } - - if ( - isNaN(Number(ticketData.screeningId)) || - isNaN(Number(ticketData.customerId)) - ) { - throw new DataNotFoundError("No data found for screening or customer ID"); - } - - [screening, customer] = await prisma.$transaction([ - prisma.screening.findUnique({ - where: { - id: Number(ticketData.screeningId), - }, - }), - prisma.customer.findUnique({ - where: { - id: Number(ticketData.customerId), - }, - }), - ]); - - if (!screening || !customer) { - let errorString = "No data found for: "; - if (!screening) { - errorString += "screening "; - } - if (!customer) { - errorString += "customer"; - } - throw new DataNotFoundError(errorString); - } - const ticket = await createTicketDb(ticketData); const result = { diff --git a/test/api/extensions/tickets-ext.spec.js b/test/api/extensions/tickets-ext.spec.js index ead1337b..ca305a8f 100644 --- a/test/api/extensions/tickets-ext.spec.js +++ b/test/api/extensions/tickets-ext.spec.js @@ -36,35 +36,11 @@ describe("Tickets Endpoint", () => { const response = await supertest(app).post(`/tickets`).send(request); - expect(response.status).toEqual(201); - expect(response.body.ticket.id).not.toBe(undefined); - expect(response.body.ticket.customer).not.toBe(undefined); - expect(response.body.ticket.screen).not.toBe(undefined); - expect(response.body.ticket.movie).not.toBe(undefined); - }); - - it("should throw an error if customerId or screeningId do not exist", async () => { - request = { - screeningId: 999, - customerId: 999, - }; - - const response = await supertest(app).post(`/tickets`).send(request); - expect(response.body.error).toEqual( - "No data found for: screening customer" - ); - }); - - it("should throw an error if screen ID or customer ID are the wrong data type", async () => { - request = { - screeningId: "cheese", - customerId: 999, - }; - - const response = await supertest(app).post(`/tickets`).send(request); - expect(response.body.error).toEqual( - "No data found for screening or customer ID" - ); + expect(response.status).toEqual(201) + expect(response.body.ticket.id).not.toBe(undefined) + expect(response.body.ticket.customer).not.toBe(undefined) + expect(response.body.ticket.screen).not.toBe(undefined) + expect(response.body.ticket.movie).not.toBe(undefined) }); }); }); From 4c663201a9694ce420bd23ae0ae923e641b9cdff Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 17:14:51 +0100 Subject: [PATCH 21/25] Refactored to simplify ticket data validation --- src/controllers/tickets.js | 14 +++++++++++--- src/domains/tickets.js | 5 ++--- test/api/extensions/tickets-ext.spec.js | 12 +++++++++++- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/controllers/tickets.js b/src/controllers/tickets.js index 47b05a77..4d07b7ce 100644 --- a/src/controllers/tickets.js +++ b/src/controllers/tickets.js @@ -2,14 +2,22 @@ const { createTicketDb } = require("../domains/tickets"); const { MissingFieldsError } = require("../errors/errors"); async function createTicket(req, res) { - const ticketData = req.body; + const { screeningId, customerId } = req.body; - if (!ticketData.screeningId || !ticketData.customerId) { + if (!screeningId || !customerId) { throw new MissingFieldsError( "Tickets require a screening ID and a customer ID" ); } - const ticket = await createTicketDb(ticketData); + + let ticket + try { + ticket = await createTicketDb(screeningId, customerId); + } catch (e) { + if(e.message === 'P2003') { + res.status(400).json({ error: 'Screen or customer ID cannot be found'}) + } + } const result = { id: ticket.id, diff --git a/src/domains/tickets.js b/src/domains/tickets.js index 8e88e9e3..dcc98bed 100644 --- a/src/domains/tickets.js +++ b/src/domains/tickets.js @@ -1,7 +1,6 @@ const prisma = require("../utils/prisma"); -async function createTicketDb(ticketData) { - const { screeningId, customerId } = ticketData; +async function createTicketDb(screeningId, customerId) { const request = { data: { @@ -27,7 +26,7 @@ async function createTicketDb(ticketData) { try { ticket = await prisma.ticket.create(request); } catch (e) { - console.log(e); + throw Error('P2003') } return ticket; diff --git a/test/api/extensions/tickets-ext.spec.js b/test/api/extensions/tickets-ext.spec.js index ca305a8f..0d299e9c 100644 --- a/test/api/extensions/tickets-ext.spec.js +++ b/test/api/extensions/tickets-ext.spec.js @@ -1,6 +1,5 @@ const supertest = require("supertest"); const app = require("../../../src/server.js"); -const { createTicket } = require("../../helpers/createTicket.js"); const { createCustomer } = require("../../helpers/createCustomer.js"); const { createScreening } = require("../../helpers/createScreening.js"); const { createScreen } = require("../../helpers/createScreen.js"); @@ -42,5 +41,16 @@ describe("Tickets Endpoint", () => { expect(response.body.ticket.screen).not.toBe(undefined) expect(response.body.ticket.movie).not.toBe(undefined) }); + + it('should throw an error if screening or customer ID cannot be found', async () => { + request = { + screeningId: 2343452, + customerId: 123313 + } + + const response = await supertest(app).post(`/tickets`).send(request); + expect(response.status).toEqual(400) + expect(response.body.error).toEqual('Screen or customer ID cannot be found') + }) }); }); From 1d2038da53fcfd04086a49ffde2ad9b8564bac73 Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 17:43:40 +0100 Subject: [PATCH 22/25] Refactored error handling for creatMovie to reduce DB calls --- .../migration.sql | 8 ++++++++ prisma/schema.prisma | 2 +- src/controllers/movies.js | 19 +++++++++++++------ test/api/extensions/movies-ext.spec.js | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 prisma/migrations/20240702162530_movie_title_unique_field/migration.sql diff --git a/prisma/migrations/20240702162530_movie_title_unique_field/migration.sql b/prisma/migrations/20240702162530_movie_title_unique_field/migration.sql new file mode 100644 index 00000000..f5af5dc9 --- /dev/null +++ b/prisma/migrations/20240702162530_movie_title_unique_field/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[title]` on the table `Movie` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Movie_title_key" ON "Movie"("title"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 892ba413..2c20c573 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -33,7 +33,7 @@ model Contact { model Movie { id Int @id @default(autoincrement()) screenings Screening[] - title String + title String @unique runtimeMins Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 68f4c6fa..35ce0eb0 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -24,19 +24,26 @@ async function getMovies(req, res) { async function createMovie(req, res) { const newMovie = req.body; - const movies = await getMoviesDb(); + // const movies = await getMoviesDb(); - if (movies.find((movie) => movie.title === newMovie.title)) { - throw new DataAlreadyExistsError("A movie with that title already exists"); - } + // if (movies.find((movie) => movie.title === newMovie.title)) { + // throw new DataAlreadyExistsError("A movie with that title already exists"); + // } const requiredFields = ["title", "runtimeMins"]; if (!requiredFields.every((field) => newMovie[field])) { throw new MissingFieldsError("Movies require a title and runtime"); } - const movie = await createMovieDb(newMovie); - res.status(201).json({ movie }); + try { + const movie = await createMovieDb(newMovie); + res.status(201).json({ movie }); + } catch (e) { + if(e.code === 'P2002') { + res.status(409).json({ error: "A movie with that title already exists"}) + } + } + } async function getMovieById(req, res) { diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index fe5967d1..caa06552 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -60,7 +60,7 @@ describe("Movies Endpoint", () => { await createMovie("Spaceballs", 140, screen); const request = { - title: "Spaceballs", + title: "The Exorcist", runtimeMins: 140, }; From 798b853602ba0b4d42d3760ef445820be1a7172a Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Tue, 2 Jul 2024 18:15:01 +0100 Subject: [PATCH 23/25] Refactored movies controllers to reduce DB calls --- src/controllers/movies.js | 48 +++++++++++-------------- src/domains/movies.js | 2 +- test/api/extensions/movies-ext.spec.js | 4 ++- test/api/extensions/screens-ext.spec.js | 1 - 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 35ce0eb0..5a526bb5 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -8,8 +8,6 @@ const { } = require("../domains/movies.js"); const { MissingFieldsError, - DataAlreadyExistsError, - DataNotFoundError } = require("../errors/errors.js"); async function getMovies(req, res) { @@ -24,11 +22,6 @@ async function getMovies(req, res) { async function createMovie(req, res) { const newMovie = req.body; - // const movies = await getMoviesDb(); - - // if (movies.find((movie) => movie.title === newMovie.title)) { - // throw new DataAlreadyExistsError("A movie with that title already exists"); - // } const requiredFields = ["title", "runtimeMins"]; if (!requiredFields.every((field) => newMovie[field])) { @@ -39,45 +32,44 @@ async function createMovie(req, res) { const movie = await createMovieDb(newMovie); res.status(201).json({ movie }); } catch (e) { - if(e.code === 'P2002') { - res.status(409).json({ error: "A movie with that title already exists"}) + if (e.code === "P2002") { + res.status(409).json({ error: "A movie with that title already exists" }); } } - } async function getMovieById(req, res) { const id = Number(req.params.id); - const movie = await getMovieByIdDb(id); - if (!movie) { - throw new DataNotFoundError('No movie found with that ID') + try { + const movie = await getMovieByIdDb(id); + res.status(200).json({ movie }); + } catch (e) { + if (e.code === "P2025") { + res.status(404).json({ error: "No movie found with that ID" }); + } } - - res.status(200).json({ movie }); } async function updateMovieById(req, res) { const id = Number(req.params.id); const updatedProps = req.body; - const foundMovie = await getMovieByIdDb(id); - - const movies = await getMoviesDb(); - - if (movies.find((movie) => updatedProps.title === movie.title)) { - throw new DataAlreadyExistsError("A movie with that title already exists"); - } - - if (!foundMovie) { - throw new DataNotFoundError('No movie found with that ID') - } if (!updatedProps.title && !updatedProps.runtimeMins) { throw new MissingFieldsError("Updating movies requires a title or runtime"); } - const movie = await updateMovieByIdDb(id, updatedProps); - res.status(201).json({ movie }); + try { + const movie = await updateMovieByIdDb(id, updatedProps); + res.status(201).json({ movie }); + } catch (e) { + if (e.code === "P2025") { + res.status(404).json({ error: "No movie found with that ID" }); + } + if (e.code === "P2002") { + res.status(409).json({ error: "A movie with that title already exists" }); + } + } } module.exports = { diff --git a/src/domains/movies.js b/src/domains/movies.js index 59998930..97291d8a 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -20,7 +20,7 @@ async function createMovieDb(newMovie) { } async function getMovieByIdDb(movieId) { - const movie = await prisma.movie.findUnique({ + const movie = await prisma.movie.findUniqueOrThrow({ where: { id: movieId, }, diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index caa06552..33c85c37 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -34,10 +34,12 @@ describe("Movies Endpoint", () => { describe("GET /movies/:id", () => { it("will throw an error if no movie exists with given id", async () => { - const response = await supertest(app).get("/movies/30"); + const response = await supertest(app).get("/movies/450"); + console.log(response.body) expect(response.status).toEqual(404); expect(response.body.error).toEqual("No movie found with that ID"); + }); }); }); diff --git a/test/api/extensions/screens-ext.spec.js b/test/api/extensions/screens-ext.spec.js index 80c50f3e..03e151e2 100644 --- a/test/api/extensions/screens-ext.spec.js +++ b/test/api/extensions/screens-ext.spec.js @@ -15,7 +15,6 @@ describe("Screens Endpoint", () => { it('should reject post requests where given screen number already exists', async () => { const screen = await createScreen(1); - console.log(screen) const request = {number: screen.number} const response = await supertest(app).post("/screens").send(request); From 76d2e3cc32a7f9ba59aeb1abfeae6f03448683cf Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 3 Jul 2024 10:39:31 +0100 Subject: [PATCH 24/25] Reviews routes and functions for get and post --- package.json | 3 +- .../20240703083007_reviews/migration.sql | 18 ++++++ prisma/schema.prisma | 14 +++++ src/controllers/reviews.js | 21 +++++++ src/domains/movies.js | 51 +++++++++------- src/domains/reviews.js | 27 +++++++++ src/routers/reviews.js | 8 +++ src/server.js | 2 + test/api/extensions/movies-ext.spec.js | 35 ++++++++--- test/api/extensions/reviews-ext.spec.js | 58 +++++++++++++++++++ test/helpers/createReview.js | 19 ++++++ test/setupTests.js | 3 +- 12 files changed, 229 insertions(+), 30 deletions(-) create mode 100644 prisma/migrations/20240703083007_reviews/migration.sql create mode 100644 src/controllers/reviews.js create mode 100644 src/domains/reviews.js create mode 100644 src/routers/reviews.js create mode 100644 test/api/extensions/reviews-ext.spec.js create mode 100644 test/helpers/createReview.js diff --git a/package.json b/package.json index be926fb7..0962ebbb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test-movies-extensions": "npx jest -i test/api/extensions/movies-ext.spec.js --forceExit", "test-customer-extensions": "npx jest -i test/api/extensions/customer-ext.spec.js --forceExit", "test-screens-extensions": "npx jest -i test/api/extensions/screens-ext.spec.js --forceExit", - "test-tickets-extensions": "npx jest -i test/api/extensions/tickets-ext.spec.js --forceExit" + "test-tickets-extensions": "npx jest -i test/api/extensions/tickets-ext.spec.js --forceExit", + "test-reviews-extensions": "npx jest -i test/api/extensions/reviews-ext.spec.js --forceExit" }, "prisma": { "seed": "node prisma/seed.js" diff --git a/prisma/migrations/20240703083007_reviews/migration.sql b/prisma/migrations/20240703083007_reviews/migration.sql new file mode 100644 index 00000000..0add6c75 --- /dev/null +++ b/prisma/migrations/20240703083007_reviews/migration.sql @@ -0,0 +1,18 @@ +-- CreateTable +CREATE TABLE "Review" ( + "id" SERIAL NOT NULL, + "content" TEXT NOT NULL, + "title" VARCHAR(100) NOT NULL, + "customerId" INTEGER NOT NULL, + "movieId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Review_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Review" ADD CONSTRAINT "Review_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Review" ADD CONSTRAINT "Review_movieId_fkey" FOREIGN KEY ("movieId") REFERENCES "Movie"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2c20c573..1790580f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,6 +16,7 @@ model Customer { name String contact Contact? tickets Ticket[] + reviews Review[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -35,6 +36,7 @@ model Movie { screenings Screening[] title String @unique runtimeMins Int + reviews Review[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -68,3 +70,15 @@ model Ticket { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model Review { + id Int @id @default(autoincrement()) + content String @db.Text + title String @db.VarChar(100) + customerId Int + customer Customer @relation(fields: [customerId], references: [id]) + movieId Int + movie Movie @relation(fields: [movieId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} \ No newline at end of file diff --git a/src/controllers/reviews.js b/src/controllers/reviews.js new file mode 100644 index 00000000..5517df35 --- /dev/null +++ b/src/controllers/reviews.js @@ -0,0 +1,21 @@ +const { getAllReviewsDb, createReviewDb } = require("../domains/reviews") +const { MissingFieldsError } = require("../errors/errors") + +async function getAllReviews(req, res) { + const reviews = await getAllReviewsDb() + res.status(200).json( {reviews} ) +} + +async function createReview(req, res) { + const { customerId, movieId, title, content } = req.body + + if (!customerId || !movieId || !title || !content) { + throw new MissingFieldsError("Reviews require a customer ID, movie ID, title, and content") + } + + const review = await createReviewDb(customerId, movieId, title, content ) + + res.status(201).json({ review }) +} + +module.exports = { getAllReviews, createReview } \ No newline at end of file diff --git a/src/domains/movies.js b/src/domains/movies.js index 97291d8a..0e9c27e7 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -1,12 +1,23 @@ const prisma = require("../utils/prisma"); async function getMoviesDb() { - const movies = await prisma.movie.findMany({ - include: { - screenings: true, - }, - }); - return movies; + let now = new Date(); + console.log(now) + const movies = await prisma.movie.findMany({ + where: { + screenings: { + some: { + startsAt: { + gt: now, + }, + }, + }, + }, + include: { + screenings: true, + }, + }); + return movies; } async function createMovieDb(newMovie) { @@ -38,26 +49,26 @@ async function updateMovieByIdDb(movieId, updatedProps) { }, data: updatedProps, include: { - screenings: true - } + screenings: true, + }, }); return movie; } async function getMoviesWithQueryDb(query) { - const { runtimeLt, runtimeGt } = query + const { runtimeLt, runtimeGt } = query; - const runTimeLimits = { - ...(runtimeLt && { lt: Number(runtimeLt) }), - ...(runtimeGt && { gt: Number(runtimeGt) }) - }; + const runTimeLimits = { + ...(runtimeLt && { lt: Number(runtimeLt) }), + ...(runtimeGt && { gt: Number(runtimeGt) }), + }; - const movies = await prisma.movie.findMany({ - where: { - runtimeMins: runTimeLimits - } - }) - return movies + const movies = await prisma.movie.findMany({ + where: { + runtimeMins: runTimeLimits, + }, + }); + return movies; } module.exports = { @@ -65,5 +76,5 @@ module.exports = { createMovieDb, getMovieByIdDb, updateMovieByIdDb, - getMoviesWithQueryDb + getMoviesWithQueryDb, }; diff --git a/src/domains/reviews.js b/src/domains/reviews.js new file mode 100644 index 00000000..575158f0 --- /dev/null +++ b/src/domains/reviews.js @@ -0,0 +1,27 @@ +const prisma = require("../utils/prisma"); + +async function getAllReviewsDb() { + return await prisma.review.findMany({ + include: { + customer: true, + movie: true + } + }) +} + +async function createReviewDb(customerId, movieId, title, content) { + return await prisma.review.create({ + data: { + movieId: movieId, + customerId: customerId, + title: title, + content: content + }, + include: { + movie: true, + customer: true + } + }) +} + +module.exports = { getAllReviewsDb, createReviewDb } \ No newline at end of file diff --git a/src/routers/reviews.js b/src/routers/reviews.js new file mode 100644 index 00000000..53914e75 --- /dev/null +++ b/src/routers/reviews.js @@ -0,0 +1,8 @@ +const express = require('express') +const router = express.Router() +const { getAllReviews, createReview } = require("../controllers/reviews.js") + +router.get("/", getAllReviews) +router.post("/", createReview) + +module.exports = router \ No newline at end of file diff --git a/src/server.js b/src/server.js index f29c25e9..ca25b2f5 100644 --- a/src/server.js +++ b/src/server.js @@ -19,10 +19,12 @@ const customerRouter = require('./routers/customer'); const movieRouter = require('./routers/movies') const screenRouter = require('./routers/screens') const ticketsRouter = require('./routers/tickets.js') +const reviewsRouter = require("./routers/reviews.js") app.use('/customers', customerRouter); app.use('/movies', movieRouter); app.use('/screens', screenRouter); app.use('/tickets', ticketsRouter); +app.use('/reviews', reviewsRouter) const { MissingFieldsError, DataAlreadyExistsError, DataNotFoundError } = require("./errors/errors.js") diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js index 33c85c37..4ced224e 100644 --- a/test/api/extensions/movies-ext.spec.js +++ b/test/api/extensions/movies-ext.spec.js @@ -3,8 +3,26 @@ const app = require("../../../src/server.js"); const { createMovie } = require("../../helpers/createMovie.js"); const { createScreen } = require("../../helpers/createScreen.js"); +const { createScreening } = require("../../helpers/createScreening.js") describe("Movies Endpoint", () => { + + describe('GET /movies', () => { + it("will only return movies with a screening that has a start time in the future", async () => { + const yesterday = new Date() + yesterday.setDate(yesterday.getDate() - 1) + + + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + const screening = await createScreening(movie, screen, yesterday) + + const response = await supertest(app).get("/movies") + expect(response.body.movies.length).toEqual(0) + + }) + }) + describe("GET /movies/runtimeLt", () => { it("will return movies less than a certain runtime", async () => { const screen = await createScreen(1); @@ -16,6 +34,7 @@ describe("Movies Endpoint", () => { expect(response.status).toEqual(200); expect(response.body.movies.length).toEqual(1); expect(response.body.movies[0].runtimeMins).toBeLessThan(115); + }); }); @@ -35,11 +54,9 @@ describe("Movies Endpoint", () => { describe("GET /movies/:id", () => { it("will throw an error if no movie exists with given id", async () => { const response = await supertest(app).get("/movies/450"); - console.log(response.body) expect(response.status).toEqual(404); expect(response.body.error).toEqual("No movie found with that ID"); - }); }); }); @@ -92,10 +109,12 @@ describe("Movies Endpoint", () => { const created = await createMovie("Dodgeball", 120, screen); const request = { - cheese: "cheddar" + cheese: "cheddar", }; - const response = await supertest(app).put(`/movies/${created.id}`).send(request); + const response = await supertest(app) + .put(`/movies/${created.id}`) + .send(request); expect(response.status).toEqual(400); expect(response.body.error).toEqual( @@ -106,13 +125,15 @@ describe("Movies Endpoint", () => { it("will throw an error if a movie already exists with that name", async () => { const screen = await createScreen(1); const created = await createMovie("Dodgeball", 120, screen); - const created2 = await createMovie("Signs", 109, screen) + const created2 = await createMovie("Signs", 109, screen); const request = { - title: "Dodgeball" + title: "Dodgeball", }; - const response = await supertest(app).put(`/movies/${created2.id}`).send(request); + const response = await supertest(app) + .put(`/movies/${created2.id}`) + .send(request); expect(response.status).toEqual(409); expect(response.body.error).toEqual( diff --git a/test/api/extensions/reviews-ext.spec.js b/test/api/extensions/reviews-ext.spec.js new file mode 100644 index 00000000..6d2e3ca1 --- /dev/null +++ b/test/api/extensions/reviews-ext.spec.js @@ -0,0 +1,58 @@ +const supertest = require("supertest"); +const app = require("../../../src/server.js"); +const { createReview } = require("../../helpers/createReview.js"); +const { createCustomer } = require("../../helpers/createCustomer.js"); +const { createMovie } = require("../../helpers/createMovie.js"); + +describe("Reviews Endpoint", () => { + describe("GET reviews", () => { + it("should return all reviews", async () => { + const customer = await createCustomer( + "Johnny", + "07949969106", + "jonny@monny.com" + ); + const movie = await createMovie("The Hunt for Red October", 230); + const review = await createReview( + customer, + movie, + "A really sorry affair", + "It just insists upon itself" + ); + const review2 = await createReview( + customer, + movie, + "An update...", + "I still think it just insists upon itself" + ); + + const response = await supertest(app).get(`/reviews`); + expect(response.body.reviews.length).toEqual(2); + expect(response.body.reviews[0].title).toEqual("A really sorry affair"); + }); + }); + + describe("POST review", () => { + it("should allow customers to create reviews", async () => { + const customer = await createCustomer( + "Johnny", + "07949969106", + "jonny@monny.com" + ); + const movie = await createMovie("The Hunt for Red October", 230); + + const request = { + customerId: customer.id, + movieId: movie.id, + title: "Why I dislike this film", + content: "It's just poor", + }; + + const response = await supertest(app).post(`/reviews`).send(request); + + expect(response.body.review.content).toEqual("It's just poor") + expect(response.body.review.movie).not.toEqual(undefined) + expect(response.body.review.customer).not.toEqual(undefined) + }); + }); +}); diff --git a/test/helpers/createReview.js b/test/helpers/createReview.js new file mode 100644 index 00000000..bb30274a --- /dev/null +++ b/test/helpers/createReview.js @@ -0,0 +1,19 @@ +const prisma = require("../../src/utils/prisma"); + + +async function createReview(customer, movie, title, content) { + return await prisma.review.create({ + data: { + movieId: movie.id, + customerId: customer.id, + title: title, + content: content + }, + include: { + movie: true, + customer: true + } + }) +} + +module.exports = { createReview } \ No newline at end of file diff --git a/test/setupTests.js b/test/setupTests.js index 2be95321..f422dbb6 100644 --- a/test/setupTests.js +++ b/test/setupTests.js @@ -2,6 +2,7 @@ const prisma = require("../src/utils/prisma") const deleteTables = () => { const deleteTables = [ + prisma.review.deleteMany(), prisma.ticket.deleteMany(), prisma.screening.deleteMany(), prisma.movie.deleteMany(), @@ -10,8 +11,6 @@ const deleteTables = () => { prisma.customer.deleteMany(), ]; - // Conditionally delete this table as this will only exist if "Extensions to the Extensions" bullet 2 is implemented - prisma.reviews && deleteTables.push(prisma.reviews.deleteMany()) return prisma.$transaction(deleteTables) } From c4dbf5caeeeaddaab6063f136fcac56dd483970d Mon Sep 17 00:00:00 2001 From: Will Baxter Date: Wed, 3 Jul 2024 11:51:41 +0100 Subject: [PATCH 25/25] Fixed getMovies controller to rerturn screenings accurately --- src/controllers/movies.js | 1 + src/domains/movies.js | 6 +- test/api/routes/movies.spec.js | 183 +++++++++++++++++---------------- 3 files changed, 99 insertions(+), 91 deletions(-) diff --git a/src/controllers/movies.js b/src/controllers/movies.js index 5a526bb5..53a385a5 100644 --- a/src/controllers/movies.js +++ b/src/controllers/movies.js @@ -15,6 +15,7 @@ async function getMovies(req, res) { if (Object.keys(req.query).length > 0) { movies = await getMoviesWithQueryDb(req.query); } else { + movies = await getMoviesDb(); } res.status(200).json({ movies }); diff --git a/src/domains/movies.js b/src/domains/movies.js index 0e9c27e7..3648cfb7 100644 --- a/src/domains/movies.js +++ b/src/domains/movies.js @@ -2,7 +2,7 @@ const prisma = require("../utils/prisma"); async function getMoviesDb() { let now = new Date(); - console.log(now) + const movies = await prisma.movie.findMany({ where: { screenings: { @@ -14,8 +14,8 @@ async function getMoviesDb() { }, }, include: { - screenings: true, - }, + screenings: true + } }); return movies; } diff --git a/test/api/routes/movies.spec.js b/test/api/routes/movies.spec.js index 209c4bc3..8ce7b735 100644 --- a/test/api/routes/movies.spec.js +++ b/test/api/routes/movies.spec.js @@ -1,90 +1,97 @@ -const supertest = require("supertest") -const app = require("../../../src/server.js") -const { createMovie } = require("../../helpers/createMovie.js") -const { createScreen } = require("../../helpers/createScreen.js") +const supertest = require("supertest"); +const app = require("../../../src/server.js"); +const { createMovie } = require("../../helpers/createMovie.js"); +const { createScreen } = require("../../helpers/createScreen.js"); +const { createScreening } = require("../../helpers/createScreening.js"); describe("Movies Endpoint", () => { - describe("GET /movies", () => { - it("will retrieve a list of movies", async () => { - const screen = await createScreen(1) - await createMovie('Dodgeball', 120, screen) - await createMovie('Scream', 113, screen) - - const response = await supertest(app).get('/movies') - - expect(response.status).toEqual(200) - expect(response.body.movies).not.toEqual(undefined) - expect(response.body.movies.length).toEqual(2) - - const [movie1, movie2] = response.body.movies - expect(movie1.title).toEqual('Dodgeball') - expect(movie1.runtimeMins).toEqual(120) - expect(movie1.screenings).not.toEqual(undefined) - expect(movie1.screenings.length).toEqual(1) - - expect(movie2.title).toEqual('Scream') - expect(movie2.runtimeMins).toEqual(113) - expect(movie2.screenings).not.toEqual(undefined) - expect(movie2.screenings.length).toEqual(1) - }) - }) - - describe("POST /movies", () => { - it("will create a movie", async () => { - const request = { - title: "Top Gun", - runtimeMins: 110 - } - - const response = await supertest(app) - .post("/movies") - .send(request) - - expect(response.status).toEqual(201) - expect(response.body.movie).not.toEqual(undefined) - expect(response.body.movie.title).toEqual('Top Gun') - expect(response.body.movie.runtimeMins).toEqual(110) - expect(response.body.movie.screenings).not.toEqual(undefined) - expect(response.body.movie.screenings.length).toEqual(0) - }) - }) - - describe("GET /movies/:id", () => { - it("will get a movie by id", async () => { - const screen = await createScreen(1) - const created = await createMovie('Dodgeball', 120, screen) - - const response = await supertest(app).get(`/movies/${created.id}`) - - expect(response.status).toEqual(200) - expect(response.body.movie).not.toEqual(undefined) - expect(response.body.movie.title).toEqual('Dodgeball') - expect(response.body.movie.runtimeMins).toEqual(120) - expect(response.body.movie.screenings).not.toEqual(undefined) - expect(response.body.movie.screenings.length).toEqual(1) - }) - }) - - describe("PUT /movies/:id", () => { - it("will update a movie by id", async () => { - const screen = await createScreen(1) - const created = await createMovie('Dodgeball', 120, screen) - - const request = { - title: 'Scream', - runtimeMins: 113 - } - - const response = await supertest(app) - .put(`/movies/${created.id}`) - .send(request) - - expect(response.status).toEqual(201) - expect(response.body.movie).not.toEqual(undefined) - expect(response.body.movie.title).toEqual('Scream') - expect(response.body.movie.runtimeMins).toEqual(113) - expect(response.body.movie.screenings).not.toEqual(undefined) - expect(response.body.movie.screenings.length).toEqual(1) - }) - }) -}) + describe("GET /movies", () => { + it("will retrieve a list of movies", async () => { + const screen = await createScreen(1); + + const firstMovie = await createMovie("Dodgeball", 120, screen); + const secondMovie = await createMovie("Scream", 113, screen); + + const tomorrow = new Date() + tomorrow.setDate(tomorrow.getDate() + 1); + + await createScreening(firstMovie, screen, tomorrow); + await createScreening(secondMovie, screen, tomorrow); + + const response = await supertest(app).get("/movies"); + + expect(response.status).toEqual(200); + expect(response.body.movies).not.toEqual(undefined); + expect(response.body.movies.length).toEqual(2); + + const [movie1, movie2] = response.body.movies; + expect(movie1.title).toEqual(firstMovie.title); + expect(movie1.runtimeMins).toEqual(120); + expect(movie1.screenings).not.toEqual(undefined); + console.log(movie1) + expect(movie1.screenings.length).toEqual(2); + + expect(movie2.title).toEqual(secondMovie.title); + expect(movie2.runtimeMins).toEqual(113); + expect(movie2.screenings).not.toEqual(undefined); + expect(movie2.screenings.length).toEqual(2); + }); + }); + + describe("POST /movies", () => { + it("will create a movie", async () => { + const request = { + title: "Top Gun", + runtimeMins: 110, + }; + + const response = await supertest(app).post("/movies").send(request); + + expect(response.status).toEqual(201); + expect(response.body.movie).not.toEqual(undefined); + expect(response.body.movie.title).toEqual("Top Gun"); + expect(response.body.movie.runtimeMins).toEqual(110); + expect(response.body.movie.screenings).not.toEqual(undefined); + expect(response.body.movie.screenings.length).toEqual(0); + }); + }); + + describe("GET /movies/:id", () => { + it("will get a movie by id", async () => { + const screen = await createScreen(1); + const created = await createMovie("Dodgeball", 120, screen); + + const response = await supertest(app).get(`/movies/${created.id}`); + + expect(response.status).toEqual(200); + expect(response.body.movie).not.toEqual(undefined); + expect(response.body.movie.title).toEqual("Dodgeball"); + expect(response.body.movie.runtimeMins).toEqual(120); + expect(response.body.movie.screenings).not.toEqual(undefined); + expect(response.body.movie.screenings.length).toEqual(1); + }); + }); + + describe("PUT /movies/:id", () => { + it("will update a movie by id", async () => { + const screen = await createScreen(1); + const created = await createMovie("Dodgeball", 120, screen); + + const request = { + title: "Scream", + runtimeMins: 113, + }; + + const response = await supertest(app) + .put(`/movies/${created.id}`) + .send(request); + + expect(response.status).toEqual(201); + expect(response.body.movie).not.toEqual(undefined); + expect(response.body.movie.title).toEqual("Scream"); + expect(response.body.movie.runtimeMins).toEqual(113); + expect(response.body.movie.screenings).not.toEqual(undefined); + expect(response.body.movie.screenings.length).toEqual(1); + }); + }); +});