From 6a8951d3d22dc593beaede169655bf1c08f0a5d2 Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Mon, 1 Jul 2024 11:58:26 +0100 Subject: [PATCH 1/7] add movie, screen and customer routers, controllers and domains files --- .env.example | 6 ----- src/controllers/customer.js | 39 +++++++++++++---------------- src/controllers/movie.js | 46 ++++++++++++++++++++++++++++++++++ src/controllers/screen.js | 12 +++++++++ src/domains/customer.js | 49 +++++++++++++++++++++--------------- src/domains/movie.js | 50 +++++++++++++++++++++++++++++++++++++ src/domains/screen.js | 10 ++++++++ src/routers/customer.js | 18 ++++++------- src/routers/movie.js | 15 +++++++++++ src/routers/screen.js | 7 ++++++ src/server.js | 28 +++++++++++---------- 11 files changed, 209 insertions(+), 71 deletions(-) delete mode 100644 .env.example create mode 100644 src/controllers/movie.js create mode 100644 src/controllers/screen.js create mode 100644 src/domains/movie.js create mode 100644 src/domains/screen.js create mode 100644 src/routers/movie.js create mode 100644 src/routers/screen.js 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" diff --git a/src/controllers/customer.js b/src/controllers/customer.js index 775cfb42..3ab010ca 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -1,12 +1,8 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") -const { createCustomerDb } = require('../domains/customer.js') +const { createCustomerDb, updateCostumerDb } = 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({ @@ -14,28 +10,16 @@ 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.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" }) } } @@ -43,6 +27,17 @@ const createCustomer = async (req, res) => { } } +const updateCostumer = async (req, res) => { + const id = Number(req.params.id) + const { name } = req.body + const customer = await updateCostumerDb(id, name) + + res.status(201).json({ + customer: customer + }) +} + module.exports = { - createCustomer + createCustomer, + updateCostumer } diff --git a/src/controllers/movie.js b/src/controllers/movie.js new file mode 100644 index 00000000..98b20cda --- /dev/null +++ b/src/controllers/movie.js @@ -0,0 +1,46 @@ +const { getAllMoviesDb, createMovieDb, getMovieByIdDb, updateMovieDb } = require("../domains/movie") + +const getAllMovies = async (req, res) => { + const movies = await getAllMoviesDb() + + res.json({ + movies: movies + }) +} + +const createMovie = async (req, res) => { + const { title, runtimeMins } = req.body + + const movie = await createMovieDb(title, runtimeMins) + + res.status(201).json({ + movie: movie + }) +} + +const getMovieById = async (req, res) => { + const id = Number(req.params.id) + const movie = await getMovieByIdDb(id) + + res.json({ + movie: movie + }) +} + +const updateMovie = async (req, res) => { + const id = Number(req.params.id) + const { title, runtimeMins } = req.body + + const movie = await updateMovieDb(id, title, runtimeMins) + + res.status(201).json({ + movie: movie + }) +} + +module.exports = { + getAllMovies, + createMovie, + getMovieById, + updateMovie +} diff --git a/src/controllers/screen.js b/src/controllers/screen.js new file mode 100644 index 00000000..f18e1d63 --- /dev/null +++ b/src/controllers/screen.js @@ -0,0 +1,12 @@ +const createSceenDb = require("../domains/screen") + +const createScreen = async (req, res) => { + const { number } = req.body + const screen = await createSceenDb(number) + + res.status(201).json({ + screen: screen + }) +} + +module.exports = createScreen diff --git a/src/domains/customer.js b/src/domains/customer.js index c7f315fd..9c0201a7 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -1,26 +1,35 @@ -const prisma = require('../utils/prisma') +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, - contact: { - create: { - phone, - email +const createCustomerDb = async (name, phone, email) => + await prisma.customer.create({ + data: { + name, + contact: { + create: { + phone, + email + } } + }, + include: { + contact: true } - }, - // 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 - } -}) + }) + +const updateCostumerDb = async (id, name) => + await prisma.customer.update({ + where: { + id: id + }, + data: { + name: name + }, + include: { + contact: true + } + }) module.exports = { - createCustomerDb + createCustomerDb, + updateCostumerDb } diff --git a/src/domains/movie.js b/src/domains/movie.js new file mode 100644 index 00000000..d582862c --- /dev/null +++ b/src/domains/movie.js @@ -0,0 +1,50 @@ +const prisma = require("../utils/prisma") + +const getAllMoviesDb = async () => + await prisma.movie.findMany({ + include: { + screenings: true + } + }) + +const createMovieDb = async (title, runtimeMins) => + await prisma.movie.create({ + data: { + title: title, + runtimeMins: runtimeMins + }, + include: { + screenings: true + } + }) + +const getMovieByIdDb = async (id) => + await prisma.movie.findUnique({ + where: { + id: id + }, + include: { + screenings: true + } + }) + +const updateMovieDb = async (id, title, runtimeMins) => + await prisma.movie.update({ + where: { + id: id + }, + data: { + title: title, + runtimeMins: runtimeMins + }, + include: { + screenings: true + } + }) + +module.exports = { + getAllMoviesDb, + createMovieDb, + getMovieByIdDb, + updateMovieDb +} diff --git a/src/domains/screen.js b/src/domains/screen.js new file mode 100644 index 00000000..cd2ffae8 --- /dev/null +++ b/src/domains/screen.js @@ -0,0 +1,10 @@ +const prisma = require("../utils/prisma") + +const createSceenDb = async (number) => + await prisma.screen.create({ + data: { + number: number + } + }) + +module.exports = createSceenDb diff --git a/src/routers/customer.js b/src/routers/customer.js index f14a87fc..73f6b642 100644 --- a/src/routers/customer.js +++ b/src/routers/customer.js @@ -1,13 +1,11 @@ -const express = require("express"); +const express = require("express") const { - createCustomer -} = require('../controllers/customer'); + createCustomer, + updateCostumer +} = require('../controllers/customer') +const router = express.Router() -const router = express.Router(); +router.post("/register", createCustomer) +router.put("/:id", updateCostumer) -// 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); - -module.exports = router; +module.exports = router diff --git a/src/routers/movie.js b/src/routers/movie.js new file mode 100644 index 00000000..dc37f61f --- /dev/null +++ b/src/routers/movie.js @@ -0,0 +1,15 @@ +const express = require("express") +const { + getAllMovies, + createMovie, + getMovieById, + updateMovie, +} = require("../controllers/movie") +const router = express.Router() + +router.get("/", getAllMovies) +router.post("/", createMovie) +router.get("/:id", getMovieById) +router.put("/:id", updateMovie) + +module.exports = router diff --git a/src/routers/screen.js b/src/routers/screen.js new file mode 100644 index 00000000..9705b64d --- /dev/null +++ b/src/routers/screen.js @@ -0,0 +1,7 @@ +const express = require("express") +const createScreen = require("../controllers/screen") +const router = express.Router() + +router.post("/", createScreen) + +module.exports = router diff --git a/src/server.js b/src/server.js index 93d47a16..754fce81 100644 --- a/src/server.js +++ b/src/server.js @@ -1,21 +1,23 @@ -const express = require('express'); -const app = express(); +const express = require("express") +const app = express() -const cors = require('cors'); -const morgan = require('morgan'); +const cors = require("cors") +const morgan = require("morgan") -app.disable('x-powered-by'); +app.disable("x-powered-by") // Add middleware -app.use(cors()); -app.use(morgan('dev')); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - +app.use(cors()) +app.use(morgan("dev")) +app.use(express.json()) +app.use(express.urlencoded({ extended: true })) // Tell express to use your routers here -const customerRouter = require('./routers/customer'); -app.use('/customers', customerRouter); - +const customerRouter = require("./routers/customer") +const movieRouter = require("./routers/movie") +const screenRouter = require("./routers/screen") +app.use("/movies", movieRouter) +app.use("/customers", customerRouter) +app.use("/screens", screenRouter) module.exports = app From 188c2ed5e80850a2bf6678a88ef840b69acd17cd Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Tue, 2 Jul 2024 12:57:47 +0100 Subject: [PATCH 2/7] add error handling and new info for http methods --- package-lock.json | 9 ++ package.json | 1 + src/controllers/customer.js | 43 ++---- src/controllers/movie.js | 72 +++++---- src/controllers/screen.js | 4 +- src/controllers/ticket.js | 12 ++ src/domains/customer.js | 71 +++++++-- src/domains/movie.js | 181 ++++++++++++++++++++--- src/domains/screen.js | 47 +++++- src/domains/ticket.js | 50 +++++++ src/errors/BadRequest.js | 3 + src/errors/Conflict.js | 3 + src/errors/NotFound.js | 3 + src/routers/movie.js | 4 +- src/routers/ticket.js | 7 + src/server.js | 56 +++++++ test/api/extensions/customer-ext.spec.js | 98 ++++++------ test/api/routes/customer.spec.js | 106 ++++++------- test/api/routes/movies.spec.js | 128 ++++++++-------- test/api/routes/screens.spec.js | 24 ++- 20 files changed, 637 insertions(+), 285 deletions(-) create mode 100644 src/controllers/ticket.js create mode 100644 src/domains/ticket.js create mode 100644 src/errors/BadRequest.js create mode 100644 src/errors/Conflict.js create mode 100644 src/errors/NotFound.js create mode 100644 src/routers/ticket.js 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 ebaf03be..35df3710 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,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/customer.js b/src/controllers/customer.js index 3ab010ca..9a3d07fc 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -1,43 +1,28 @@ -const { PrismaClientKnownRequestError } = require("@prisma/client") -const { createCustomerDb, updateCostumerDb } = require("../domains/customer.js") +const { + createCustomerDb, + updateCostumerDb, +} = require("../domains/customer.js") const createCustomer = async (req, res) => { const { name, phone, email } = req.body + const createdCustomer = await createCustomerDb(name, phone, email) - if (!name || !phone || !email) { - return res.status(400).json({ - error: "Missing fields in request body" - }) - } - - try { - const createdCustomer = await createCustomerDb(name, phone, email) - - res.status(201).json({ customer: createdCustomer }) - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - if (e.code === "P2002") { - return res - .status(409) - .json({ error: "A customer with the provided email already exists" }) - } - } - - res.status(500).json({ error: e.message }) - } + res.status(201).json({ + customer: createdCustomer, + }) } const updateCostumer = async (req, res) => { - const id = Number(req.params.id) - const { name } = req.body - const customer = await updateCostumerDb(id, name) - + const paramsId = Number(req.params.id) + const { name, contact } = req.body + const customer = await updateCostumerDb(paramsId, name, contact) + res.status(201).json({ - customer: customer + customer: customer, }) } module.exports = { createCustomer, - updateCostumer + updateCostumer, } diff --git a/src/controllers/movie.js b/src/controllers/movie.js index 98b20cda..6fa40384 100644 --- a/src/controllers/movie.js +++ b/src/controllers/movie.js @@ -1,46 +1,58 @@ -const { getAllMoviesDb, createMovieDb, getMovieByIdDb, updateMovieDb } = require("../domains/movie") +const { + getAllMoviesDb, + createMovieDb, + getMovieByIdOrTitleDb, + updateMovieDb, +} = require("../domains/movie") const getAllMovies = async (req, res) => { - const movies = await getAllMoviesDb() + const runtimeLt = Number(req.query.runtimeLt) + const runtimeGt = Number(req.query.runtimeGt) + const movies = await getAllMoviesDb(runtimeLt, runtimeGt) - res.json({ - movies: movies - }) + res.json({ + movies: movies, + }) } const createMovie = async (req, res) => { - const { title, runtimeMins } = req.body + const { title, runtimeMins, screenings } = req.body + const movie = await createMovieDb(title, runtimeMins, screenings) - const movie = await createMovieDb(title, runtimeMins) - - res.status(201).json({ - movie: movie - }) + res.status(201).json({ + movie: movie, + }) } -const getMovieById = async (req, res) => { - const id = Number(req.params.id) - const movie = await getMovieByIdDb(id) - - res.json({ - movie: movie - }) +const getMovieByIdOrTitle = async (req, res) => { + let id = Number(req.params.idOrTitle) + let title = undefined + + if (isNaN(id)) { + title = req.params.idOrTitle + id = undefined + } + + const movie = await getMovieByIdOrTitleDb(id, title) + + res.json({ + movie: movie, + }) } const updateMovie = async (req, res) => { - const id = Number(req.params.id) - const { title, runtimeMins } = req.body - - const movie = await updateMovieDb(id, title, runtimeMins) - - res.status(201).json({ - movie: movie - }) + const paramsId = Number(req.params.id) + const { title, runtimeMins, screenings } = req.body + const movie = await updateMovieDb(paramsId, title, runtimeMins, screenings) + + res.status(201).json({ + movie: movie, + }) } module.exports = { - getAllMovies, - createMovie, - getMovieById, - updateMovie + getAllMovies, + createMovie, + getMovieByIdOrTitle, + updateMovie, } diff --git a/src/controllers/screen.js b/src/controllers/screen.js index f18e1d63..581840f5 100644 --- a/src/controllers/screen.js +++ b/src/controllers/screen.js @@ -1,8 +1,8 @@ const createSceenDb = require("../domains/screen") const createScreen = async (req, res) => { - const { number } = req.body - const screen = await createSceenDb(number) + const { number, screenings } = req.body + const screen = await createSceenDb(number, screenings) res.status(201).json({ screen: screen diff --git a/src/controllers/ticket.js b/src/controllers/ticket.js new file mode 100644 index 00000000..c30ec12b --- /dev/null +++ b/src/controllers/ticket.js @@ -0,0 +1,12 @@ +const createTicketDb = require("../domains/ticket") + +const createTicket = async (req, res) => { + const { screeningId, customerId } = req.body + const ticket = await createTicketDb(screeningId, customerId) + + res.status(201).json({ + ticket: ticket, + }) +} + +module.exports = createTicket diff --git a/src/domains/customer.js b/src/domains/customer.js index 9c0201a7..504bd0c1 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -1,35 +1,76 @@ const prisma = require("../utils/prisma") +const BadRequest = require("../errors/BadRequest") +const NotFound = require("../errors/NotFound") -const createCustomerDb = async (name, phone, email) => - await prisma.customer.create({ +const createCustomerDb = async (name, phone, email) => { + if (!name || !phone || !email) { + throw new BadRequest("Missing fields in request body") + } + + return await prisma.customer.create({ data: { name, contact: { create: { phone, - email - } - } + email, + }, + }, }, include: { - contact: true - } + contact: true, + }, }) +} -const updateCostumerDb = async (id, name) => - await prisma.customer.update({ +const updateCostumerDb = async (paramsId, name, contact) => { + const customer = await prisma.customer.findUnique({ where: { - id: id + id: paramsId, }, - data: { - name: name + }) + + if (!customer) { + throw new NotFound("Customer with that id does not exist") + } + + if (!name) { + throw new BadRequest("Missing fields in request body") + } + + let dataClause = { + name: name, + } + + if (contact) { + const { phone, email } = contact + + if (!phone || !email) { + throw new BadRequest("Missing fields in request body") + } + + dataClause.contact = { + update: { + data: { + phone: phone, + email: email, + }, + }, + } + } + + return await prisma.customer.update({ + where: { + id: paramsId, }, + data: dataClause, include: { - contact: true - } + contact: true, + }, }) +} module.exports = { createCustomerDb, - updateCostumerDb + updateCostumerDb, } diff --git a/src/domains/movie.js b/src/domains/movie.js index d582862c..866f0e2b 100644 --- a/src/domains/movie.js +++ b/src/domains/movie.js @@ -1,50 +1,183 @@ +const BadRequest = require("../errors/BadRequest") +const Conflict = require("../errors/Conflict") +const NotFound = require("../errors/NotFound") const prisma = require("../utils/prisma") -const getAllMoviesDb = async () => - await prisma.movie.findMany({ - include: { - screenings: true +const getAllMoviesDb = async (runtimeLt, runtimeGt) => { + let whereClause = {} + + if (runtimeLt || runtimeGt) { + whereClause = { + OR: [], } + } + + if (runtimeLt) { + whereClause.OR.push({ + runtimeMins: { + lt: runtimeLt, + }, + }) + } + + if (runtimeGt) { + whereClause.OR.push({ + runtimeMins: { + gt: runtimeGt, + }, + }) + } + + return await prisma.movie.findMany({ + where: whereClause, + include: { + screenings: true, + }, }) +} + +const createMovieDb = async (title, runtimeMins, screenings) => { + if (!title || !runtimeMins) { + throw new BadRequest("Missing fields in request body") + } -const createMovieDb = async (title, runtimeMins) => - await prisma.movie.create({ - data: { + const existingTitle = await prisma.movie.findFirst({ + where: { title: title, - runtimeMins: runtimeMins }, - include: { - screenings: true + }) + + if (existingTitle) { + throw new Conflict("A movie with the provided title already exists") + } + + let dataClause = { + title: title, + runtimeMins: runtimeMins, + } + + if (screenings) { + const { screenId, startsAt } = screenings[screenings.length - 1] + + if (!screenId || !startsAt) { + throw new BadRequest("Missing fields in request body") + } + + dataClause = { + title: title, + runtimeMins: runtimeMins, + screenings: { + create: [ + { + screenId: screenId, + startsAt: startsAt, + }, + ], + }, } + } + + return await prisma.movie.create({ + data: dataClause, + include: { + screenings: true, + }, }) +} -const getMovieByIdDb = async (id) => - await prisma.movie.findUnique({ +const getMovieByIdOrTitleDb = async (id, title) => { + const movie = await prisma.movie.findFirst({ where: { - id: id + OR: [ + { + id: id, + }, + { + title: { + equals: title, + mode: "insensitive", + }, + }, + ], }, include: { - screenings: true - } + screenings: true, + }, }) -const updateMovieDb = async (id, title, runtimeMins) => - await prisma.movie.update({ + if (!movie) { + throw new NotFound("Movie with that id or title does not exist") + } + + return movie +} + +const updateMovieDb = async (paramsId, title, runtimeMins, screenings) => { + const movie = await prisma.movie.findUnique({ where: { - id: id + id: paramsId, }, - data: { + }) + + if (!movie) { + throw new NotFound("Movie with that id does not exist") + } + + if (!title || !runtimeMins) { + throw new BadRequest("Missing fields in the request body") + } + + const existingTitle = await prisma.movie.findFirst({ + where: { title: title, - runtimeMins: runtimeMins }, - include: { - screenings: true + }) + + if (existingTitle) { + throw new Conflict("Movie with that title already exists") + } + + let dataClause = { + title: title, + runtimeMins: runtimeMins, + } + + if (screenings) { + const { screenId, startsAt, id } = screenings[screenings.length - 1] + + if (!screenId || !startsAt || !id) { + throw new BadRequest("Missing fields in the request body") } + + dataClause.screenings = { + update: [ + { + where: { + id: id, + }, + data: { + screenId: screenId, + startsAt: startsAt, + }, + }, + ], + } + } + + return await prisma.movie.update({ + where: { + id: paramsId, + }, + data: dataClause, + include: { + screenings: true, + }, }) +} module.exports = { getAllMoviesDb, createMovieDb, - getMovieByIdDb, - updateMovieDb + getMovieByIdOrTitleDb, + updateMovieDb, } diff --git a/src/domains/screen.js b/src/domains/screen.js index cd2ffae8..52cffc6a 100644 --- a/src/domains/screen.js +++ b/src/domains/screen.js @@ -1,10 +1,49 @@ +const BadRequest = require("../errors/BadRequest") +const Conflict = require("../errors/Conflict") const prisma = require("../utils/prisma") -const createSceenDb = async (number) => - await prisma.screen.create({ - data: { - number: number +const createSceenDb = async (number, screenings) => { + if (!number) { + throw new BadRequest("Missing fields in the request body") + } + + const screen = await prisma.screen.findFirst({ + where: { + number: number, + }, + }) + + if (screen) { + throw new Conflict("A screen with the provided number already exists") + } + + let dataClause = { + number: number, + } + + if (screenings) { + const { movieId, startsAt } = screenings[screenings.length - 1] + + if (!movieId || !startsAt) { + throw new BadRequest("Missing fields in the request body") + } + + dataClause.screenings = { + create: [ + { + movieId: movieId, + startsAt: startsAt, + }, + ], } + } + + return await prisma.screen.create({ + data: dataClause, + include: { + screenings: true, + }, }) +} module.exports = createSceenDb diff --git a/src/domains/ticket.js b/src/domains/ticket.js new file mode 100644 index 00000000..3760db02 --- /dev/null +++ b/src/domains/ticket.js @@ -0,0 +1,50 @@ +const BadRequest = require("../errors/BadRequest") +const NotFound = require("../errors/NotFound") +const prisma = require("../utils/prisma") + +const createTicketDb = async (screeningId, customerId) => { + if (!screeningId || !customerId) { + throw new BadRequest("Missing fields in request body") + } + + const customer = await prisma.customer.findUnique({ + where: { + id: customerId, + }, + }) + + const screening = await prisma.screening.findUnique({ + where: { + id: screeningId, + }, + }) + + if (!customer || !screening) { + throw new NotFound( + "A customer or screening does not exist with the provided id" + ) + } + + return await prisma.ticket.create({ + data: { + screeningId: screeningId, + customerId: customerId, + }, + select: { + id: true, + customer: { + include: { + contact: true, + }, + }, + screening: { + include: { + screen: true, + movie: true, + }, + }, + }, + }) +} + +module.exports = createTicketDb diff --git a/src/errors/BadRequest.js b/src/errors/BadRequest.js new file mode 100644 index 00000000..d8a4549b --- /dev/null +++ b/src/errors/BadRequest.js @@ -0,0 +1,3 @@ +class BadRequest extends Error {} + +module.exports = BadRequest diff --git a/src/errors/Conflict.js b/src/errors/Conflict.js new file mode 100644 index 00000000..76eed8e3 --- /dev/null +++ b/src/errors/Conflict.js @@ -0,0 +1,3 @@ +class Conflict extends Error {} + +module.exports = Conflict diff --git a/src/errors/NotFound.js b/src/errors/NotFound.js new file mode 100644 index 00000000..be9f2418 --- /dev/null +++ b/src/errors/NotFound.js @@ -0,0 +1,3 @@ +class NotFound extends Error {} + +module.exports = NotFound diff --git a/src/routers/movie.js b/src/routers/movie.js index dc37f61f..685b3148 100644 --- a/src/routers/movie.js +++ b/src/routers/movie.js @@ -2,14 +2,14 @@ const express = require("express") const { getAllMovies, createMovie, - getMovieById, + getMovieByIdOrTitle, updateMovie, } = require("../controllers/movie") const router = express.Router() router.get("/", getAllMovies) router.post("/", createMovie) -router.get("/:id", getMovieById) +router.get("/:idOrTitle", getMovieByIdOrTitle) router.put("/:id", updateMovie) module.exports = router diff --git a/src/routers/ticket.js b/src/routers/ticket.js new file mode 100644 index 00000000..afaf80b6 --- /dev/null +++ b/src/routers/ticket.js @@ -0,0 +1,7 @@ +const express = require("express") +const createTicket = require("../controllers/ticket") +const router = express.Router() + +router.post("/", createTicket) + +module.exports = router diff --git a/src/server.js b/src/server.js index 754fce81..8bce7e11 100644 --- a/src/server.js +++ b/src/server.js @@ -1,8 +1,15 @@ const express = require("express") +require("express-async-errors") const app = express() const cors = require("cors") const morgan = require("morgan") +const BadRequest = require("./errors/BadRequest") +const Conflict = require("./errors/Conflict") +const NotFound = require("./errors/NotFound") +const { + PrismaClientKnownRequestError, +} = require("@prisma/client/runtime/library") app.disable("x-powered-by") @@ -16,8 +23,57 @@ app.use(express.urlencoded({ extended: true })) const customerRouter = require("./routers/customer") const movieRouter = require("./routers/movie") const screenRouter = require("./routers/screen") +const ticketRouter = require("./routers/ticket") + app.use("/movies", movieRouter) app.use("/customers", customerRouter) app.use("/screens", screenRouter) +app.use("/tickets", ticketRouter) + +app.use((error, req, res, next) => { + if (error instanceof BadRequest) { + res.status(400).json({ + error: error.message, + }) + } + + if (error instanceof NotFound) { + res.status(404).json({ + error: error.message, + }) + } + + if (error instanceof Conflict) { + res.status(409).json({ + error: error.message, + }) + } + + if (error instanceof PrismaClientKnownRequestError) { + if (error.code === "P2025") { + res.status(404).json({ + error: error.meta.cause, + }) + } + + if (error.code === "P2003") { + res.status(404).json({ + error: + "Foreign key constraint failed on the field: " + + error.meta.field_name, + }) + } + + if (error.code === "P2002") { + res.status(409).json({ + error: "Unique constraint failed on the " + error.meta.target, + }) + } + } + + res.status(500).json({ + error: "Internal Server Error", + }) +}) module.exports = app diff --git a/test/api/extensions/customer-ext.spec.js b/test/api/extensions/customer-ext.spec.js index fee884bb..076dd03b 100644 --- a/test/api/extensions/customer-ext.spec.js +++ b/test/api/extensions/customer-ext.spec.js @@ -3,54 +3,54 @@ const app = require("../../../src/server.js") const { createCustomer } = require("../../helpers/createCustomer.js") describe("Customer Endpoint", () => { - describe("PUT /customers/:id", () => { - it("can update a customers contact info when a contact property exists on the request body", async () => { - const customer = await createCustomer("John", "123456", "john@test.com") - - const request = { - name: "Jane", - contact: { - phone: "789", - email: "jane@test.com" - } - } - - const response = await supertest(app) - .put(`/customers/${customer.id}`) - .send(request) - - expect(response.status).toEqual(201) - expect(response.body.customer).not.toEqual(undefined) - expect(response.body.customer.name).toEqual(request.name) - expect(response.body.customer.contact).not.toEqual(undefined) - expect(response.body.customer.contact.phone).toEqual("789") - expect(response.body.customer.contact.email).toEqual("jane@test.com") - }) - - it('will return 404 if the customer is not found', async () => { - const request = { - name: "Jane", - } - - const response = await supertest(app) - .put(`/customers/10000`) - .send(request) - - expect(response.status).toEqual(404) - expect(response.body).toHaveProperty('error') - }) - - it("will return 400 when there are missing fields in the request body", async () => { - const customer = await createCustomer("John", "123456", "john@test.com") - - const request = {} - - const response = await supertest(app) - .put(`/customers/${customer.id}`) - .send(request) - - expect(response.status).toEqual(400) - expect(response.body).toHaveProperty('error') - }) + describe("PUT /customers/:id", () => { + it("can update a customers contact info when a contact property exists on the request body", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + + const request = { + name: "Jane", + contact: { + phone: "789", + email: "jane@test.com", + }, + } + + const response = await supertest(app) + .put(`/customers/${customer.id}`) + .send(request) + + expect(response.status).toEqual(201) + expect(response.body.customer).not.toEqual(undefined) + expect(response.body.customer.name).toEqual(request.name) + expect(response.body.customer.contact).not.toEqual(undefined) + expect(response.body.customer.contact.phone).toEqual("789") + expect(response.body.customer.contact.email).toEqual("jane@test.com") }) + + it("will return 404 if the customer is not found", async () => { + const request = { + name: "Jane", + } + + const response = await supertest(app) + .put(`/customers/10000`) + .send(request) + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + + const request = {} + + const response = await supertest(app) + .put(`/customers/${customer.id}`) + .send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + }) }) diff --git a/test/api/routes/customer.spec.js b/test/api/routes/customer.spec.js index c530c8e6..ab8e07b2 100644 --- a/test/api/routes/customer.spec.js +++ b/test/api/routes/customer.spec.js @@ -3,69 +3,71 @@ const app = require("../../../src/server.js") const { createCustomer } = require("../../helpers/createCustomer.js") describe("Customer Endpoint", () => { - describe("POST /customers/register", () => { - it("will create a new customer", async () => { - const request = { - name: "john", - phone: "123456", - email: "john@test.com", - } + describe("POST /customers/register", () => { + it("will create a new customer", async () => { + const request = { + name: "john", + phone: "123456", + email: "john@test.com", + } - const response = await supertest(app) - .post("/customers/register") - .send(request) + const response = await supertest(app) + .post("/customers/register") + .send(request) - expect(response.status).toEqual(201) - expect(response.body.customer).not.toEqual(undefined) - expect(response.body.customer.id).not.toEqual(undefined) - expect(response.body.customer.name).toEqual(request.name) - expect(response.body.customer.contact.phone).toEqual(request.phone) - expect(response.body.customer.contact.email).toEqual(request.email) - }) + expect(response.status).toEqual(201) + expect(response.body.customer).not.toEqual(undefined) + expect(response.body.customer.id).not.toEqual(undefined) + expect(response.body.customer.name).toEqual(request.name) + expect(response.body.customer.contact.phone).toEqual(request.phone) + expect(response.body.customer.contact.email).toEqual(request.email) + }) - it("will return 400 if one of the required fields is missing", async () => { - const response = await supertest(app).post("/customers/register").send({}) + it("will return 400 if one of the required fields is missing", async () => { + const response = await supertest(app) + .post("/customers/register") + .send({}) - expect(response.status).toEqual(400) - expect(response.body).toHaveProperty('error') - }) + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) - it("will return 409 when attemping to register a customer with an in-use email address", async () => { - const request = { - name: "john", - phone: "123456", - email: "john@test.com", - } + it("will return 409 when attemping to register a customer with an in-use email address", async () => { + const request = { + name: "john", + phone: "123456", + email: "john@test.com", + } - await createCustomer(request.name, request.phone, request.email) + await createCustomer(request.name, request.phone, request.email) - const response = await supertest(app) - .post("/customers/register") - .send(request) + const response = await supertest(app) + .post("/customers/register") + .send(request) - expect(response.status).toEqual(409) - expect(response.body).toHaveProperty('error') - }) + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty("error") }) + }) - describe("PUT /customers/:id", () => { - it("can update a customers name", async () => { - const customer = await createCustomer("John", "123456", "john@test.com") + describe("PUT /customers/:id", () => { + it("can update a customers name", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") - const request = { - name: "Jane", - } + const request = { + name: "Jane", + } - const response = await supertest(app) - .put(`/customers/${customer.id}`) - .send(request) + const response = await supertest(app) + .put(`/customers/${customer.id}`) + .send(request) - expect(response.status).toEqual(201) - expect(response.body.customer).not.toEqual(undefined) - expect(response.body.customer.name).toEqual(request.name) - expect(response.body.customer.contact).not.toEqual(undefined) - expect(response.body.customer.contact.phone).toEqual("123456") - expect(response.body.customer.contact.email).toEqual("john@test.com") - }) + expect(response.status).toEqual(201) + expect(response.body.customer).not.toEqual(undefined) + expect(response.body.customer.name).toEqual(request.name) + expect(response.body.customer.contact).not.toEqual(undefined) + expect(response.body.customer.contact.phone).toEqual("123456") + expect(response.body.customer.contact.email).toEqual("john@test.com") }) -}) \ No newline at end of file + }) +}) diff --git a/test/api/routes/movies.spec.js b/test/api/routes/movies.spec.js index 209c4bc3..32516ed2 100644 --- a/test/api/routes/movies.spec.js +++ b/test/api/routes/movies.spec.js @@ -4,87 +4,85 @@ const { createMovie } = require("../../helpers/createMovie.js") const { createScreen } = require("../../helpers/createScreen.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) + 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') + 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) + 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) + 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) - }) + 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 - } + 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) + 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) - }) + 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) + 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}`) + 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) - }) + 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) + 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 request = { + title: "Scream", + runtimeMins: 113, + } - 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(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) - }) + 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) }) + }) }) diff --git a/test/api/routes/screens.spec.js b/test/api/routes/screens.spec.js index e6bc8da5..46be38d7 100644 --- a/test/api/routes/screens.spec.js +++ b/test/api/routes/screens.spec.js @@ -2,19 +2,17 @@ const supertest = require("supertest") const app = require("../../../src/server.js") describe("Screens Endpoint", () => { - describe("POST /screens", () => { - it("will create a new screen", async () => { - const request = { - number: 10 - } + describe("POST /screens", () => { + it("will create a new screen", async () => { + const request = { + number: 10, + } - const response = await supertest(app) - .post("/screens") - .send(request) + const response = await supertest(app).post("/screens").send(request) - expect(response.status).toEqual(201) - expect(response.body.screen).not.toEqual(undefined) - expect(response.body.screen.number).toEqual(10) - }) + expect(response.status).toEqual(201) + expect(response.body.screen).not.toEqual(undefined) + expect(response.body.screen.number).toEqual(10) }) -}) \ No newline at end of file + }) +}) From d15a573e68b135c2d8bebb76b25efc8317cf06b3 Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Tue, 2 Jul 2024 16:04:53 +0100 Subject: [PATCH 3/7] add movie tests --- test/api/extensions/movie-ext.spec.js | 165 ++++++++++++++++++++++++++ test/helpers/createCustomer.js | 2 +- 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 test/api/extensions/movie-ext.spec.js diff --git a/test/api/extensions/movie-ext.spec.js b/test/api/extensions/movie-ext.spec.js new file mode 100644 index 00000000..b35c10aa --- /dev/null +++ b/test/api/extensions/movie-ext.spec.js @@ -0,0 +1,165 @@ +const supertest = require("supertest") +const app = require("../../../src/server.js") +const { createScreen } = require("../../helpers/createScreen.js") +const { createMovie } = require("../../helpers/createMovie.js") + +describe("Movie Endpoint", () => { + describe("GET /movies", () => { + it("can retrieve all movies by the runtimeLt and runtimeGt parameters to filter the results", async () => { + const screen = await createScreen(1) + await createMovie("Dodgeball", 120, screen) + await createMovie("Scream", 153, screen) + + const response = await supertest(app).get( + "/movies?runtimeLt=125&runtimeGt=150" + ) + + expect(response.status).toEqual(200) + expect(response.body.movies).not.toEqual(undefined) + expect(response.body.movies.length).toEqual(2) + }) + }) + + describe("POST /movies", () => { + it("create screenings for the movie if the request body has a screenings property", async () => { + const screen = await createScreen(1) + + const request = { + title: "Dodgeball", + runtimeMins: 120, + screenings: [ + { + screenId: screen.id, + startsAt: "2024-07-02T11:58:26.287Z", + }, + ], + } + + 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("Dodgeball") + expect(response.body.movie.screenings[0].screenId).toEqual(screen.id) + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const request = {} + + const response = await supertest(app).post("/movies").send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + + it("will return 409 if a movie with the provided title already exists", async () => { + const screen = await createScreen(1) + await createMovie("Dodgeball", 120, screen) + + const request = { + title: "Dodgeball", + runtimeMins: 153, + screenings: [ + { + screenId: screen.id, + startsAt: "2024-07-02T11:58:26.287Z", + }, + ], + } + + const response = await supertest(app).post("/movies").send(request) + + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty("error") + }) + }) + + describe("GET /movies:idOrTitle", () => { + it("can retrieve movie by an id or a title", async () => { + const screen = await createScreen(1) + await createMovie("Dodgeball", 120, screen) + + const response = await supertest(app).get("/movies/dodgeball") + + expect(response.status).toEqual(200) + expect(response.body.movie).not.toEqual(undefined) + expect(response.body.movie.title).toEqual("Dodgeball") + }) + + it("will return 404 if a movie with that id or title does not exist", async () => { + const response = await supertest(app).get("/movies/dodgeball") + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + }) + + describe("PUT /movies/:id", () => { + it("can update screenings for the movie if the request body has a screenings property", async () => { + const screen = await createScreen(1) + const screen2 = await createScreen(2) + const movie = await createMovie("Dodgeball", 120, screen) + + const request = { + title: "Cars", + runtimeMins: 130, + screenings: [ + { + id: movie.screenings[0].id, + screenId: screen2.id, + startsAt: "2024-08-02T11:58:26.287Z", + }, + ], + } + + const response = await supertest(app) + .put(`/movies/${movie.id}`) + .send(request) + + expect(response.status).toEqual(201) + expect(response.body.movie).not.toEqual(undefined) + expect(response.body.movie.title).toEqual("Cars") + expect(response.body.movie.screenings[0].screenId).toEqual(screen2.id) + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + + const request = {} + + const response = await supertest(app) + .put(`/movies/${movie.id}`) + .send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + + it("will return 404 if a movie with that id does not exist", async () => { + const request = {} + + const response = await supertest(app).put("/movies/1").send(request) + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + }) + + it("will return 409 if a movie with the provided title already exists", async () => { + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + + const request = { + title: "Dodgeball", + runtimeMins: 153, + } + + const response = await supertest(app) + .put(`/movies/${movie.id}`) + .send(request) + + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty("error") + }) +}) diff --git a/test/helpers/createCustomer.js b/test/helpers/createCustomer.js index 2bbc5ef8..33d1cd7f 100644 --- a/test/helpers/createCustomer.js +++ b/test/helpers/createCustomer.js @@ -15,5 +15,5 @@ const createCustomer = async (name, phone, email) => { } module.exports = { - createCustomer + createCustomer } From d048259ee5395a41b8e41d8910e677f5dccf3640 Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Tue, 2 Jul 2024 16:34:17 +0100 Subject: [PATCH 4/7] add screen and ticket tests --- test/api/extensions/screen-ext.spec.js | 62 ++++++++++++++++++++++++++ test/api/extensions/ticket-ext.spec.js | 55 +++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 test/api/extensions/screen-ext.spec.js create mode 100644 test/api/extensions/ticket-ext.spec.js diff --git a/test/api/extensions/screen-ext.spec.js b/test/api/extensions/screen-ext.spec.js new file mode 100644 index 00000000..4e9c5487 --- /dev/null +++ b/test/api/extensions/screen-ext.spec.js @@ -0,0 +1,62 @@ +const supertest = require("supertest") +const app = require("../../../src/server.js") +const { createMovie } = require("../../helpers/createMovie.js") +const { createScreen } = require("../../helpers/createScreen.js") + +describe("Screen Endpoint", () => { + describe("POST /screens", () => { + it("can create screenings for a movie if the request body has a screenings property", async () => { + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + + const request = { + number: 234, + screenings: [ + { + movieId: movie.id, + startsAt: "2024-07-02T11:58:26.287Z", + }, + ], + } + + const response = await supertest(app).post("/screens").send(request) + + expect(response.status).toEqual(201) + expect(response.body.screen).not.toEqual(undefined) + expect(response.body.screen.number).toEqual(234) + expect(response.body.screen.screenings[0].movieId).toEqual(movie.id) + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + + const request = { + screenings: [ + { + movieId: movie.id, + startsAt: "2024-07-02T11:58:26.287Z", + }, + ], + } + + const response = await supertest(app).post("/screens").send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + + it("will return 409 if the provided number already exists", async () => { + const screen = await createScreen(1) + + const request = { + number: screen.number, + } + + const response = await supertest(app).post("/screens").send(request) + + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty("error") + }) + }) +}) diff --git a/test/api/extensions/ticket-ext.spec.js b/test/api/extensions/ticket-ext.spec.js new file mode 100644 index 00000000..37ea6fcf --- /dev/null +++ b/test/api/extensions/ticket-ext.spec.js @@ -0,0 +1,55 @@ +const supertest = require("supertest") +const app = require("../../../src/server.js") +const { createScreen } = require("../../helpers/createScreen.js") +const { createCustomer } = require("../../helpers/createCustomer.js") +const { createMovie } = require("../../helpers/createMovie.js") + +describe("Ticket Endpoint", () => { + describe("POST /tickets", () => { + it("can create a ticket", async () => { + const customer = await createCustomer("Leo", "1234", "leo@email") + const screen = await createScreen(1) + const movie = await createMovie("Scream", 153, screen) + + const request = { + screeningId: movie.screenings[0].id, + customerId: customer.id, + } + + const response = await supertest(app).post("/tickets").send(request) + + expect(response.status).toEqual(201) + expect(response.body.ticket).not.toEqual(undefined) + expect(response.body.ticket.customer.name).toEqual("Leo") + expect(response.body.ticket.screening.movie.title).toEqual("Scream") + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const screen = await createScreen(1) + const movie = await createMovie("Scream", 153, screen) + + const request = { + screeningId: movie.screenings[0].id, + } + + const response = await supertest(app).post("/tickets").send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + + it("will return 404 if a costumer or screening does not exist", async () => { + const customer = await createCustomer("Leo", "1234", "leo@email") + + const request = { + screeningId: 1, + customerId: customer.id, + } + + const response = await supertest(app).post("/tickets").send(request) + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + }) +}) From 9821ba5ad0826db88107683c8a817bf8563859d3 Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Tue, 2 Jul 2024 17:37:49 +0100 Subject: [PATCH 5/7] add get all movies http method only with future screenings --- src/domains/movie.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/domains/movie.js b/src/domains/movie.js index 866f0e2b..bfcea8d5 100644 --- a/src/domains/movie.js +++ b/src/domains/movie.js @@ -28,12 +28,16 @@ const getAllMoviesDb = async (runtimeLt, runtimeGt) => { }) } - return await prisma.movie.findMany({ + const movies = await prisma.movie.findMany({ where: whereClause, include: { screenings: true, }, }) + + const moviesWithScreenings = movies.filter(movie => movie.screenings.length >= 1) + + return moviesWithScreenings } const createMovieDb = async (title, runtimeMins, screenings) => { From 7a255b0bfe6fbed69e19c90c0a761bea11ced01b Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Tue, 2 Jul 2024 19:03:55 +0100 Subject: [PATCH 6/7] add review model, route, controller, domain and errors to it --- .../20240702164556_reviews/migration.sql | 16 ++ .../migration.sql | 8 + .../migration.sql | 11 ++ prisma/schema.prisma | 13 ++ prisma/seed.js | 141 ++++++++++++------ src/controllers/review.js | 62 ++++++++ src/domains/customer.js | 7 + src/domains/movie.js | 21 +++ src/domains/review.js | 110 ++++++++++++++ src/routers/review.js | 11 ++ src/server.js | 2 + test/setupTests.js | 21 +-- 12 files changed, 362 insertions(+), 61 deletions(-) create mode 100644 prisma/migrations/20240702164556_reviews/migration.sql create mode 100644 prisma/migrations/20240702164900_reviews_add_content_column/migration.sql create mode 100644 prisma/migrations/20240702180147_cascade_deletes/migration.sql create mode 100644 src/controllers/review.js create mode 100644 src/domains/review.js create mode 100644 src/routers/review.js diff --git a/prisma/migrations/20240702164556_reviews/migration.sql b/prisma/migrations/20240702164556_reviews/migration.sql new file mode 100644 index 00000000..920a6337 --- /dev/null +++ b/prisma/migrations/20240702164556_reviews/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "Review" ( + "id" SERIAL 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/migrations/20240702164900_reviews_add_content_column/migration.sql b/prisma/migrations/20240702164900_reviews_add_content_column/migration.sql new file mode 100644 index 00000000..551f13ce --- /dev/null +++ b/prisma/migrations/20240702164900_reviews_add_content_column/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `content` to the `Review` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Review" ADD COLUMN "content" TEXT NOT NULL; diff --git a/prisma/migrations/20240702180147_cascade_deletes/migration.sql b/prisma/migrations/20240702180147_cascade_deletes/migration.sql new file mode 100644 index 00000000..04381f3f --- /dev/null +++ b/prisma/migrations/20240702180147_cascade_deletes/migration.sql @@ -0,0 +1,11 @@ +-- DropForeignKey +ALTER TABLE "Review" DROP CONSTRAINT "Review_customerId_fkey"; + +-- DropForeignKey +ALTER TABLE "Review" DROP CONSTRAINT "Review_movieId_fkey"; + +-- AddForeignKey +ALTER TABLE "Review" ADD CONSTRAINT "Review_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Review" ADD CONSTRAINT "Review_movieId_fkey" FOREIGN KEY ("movieId") REFERENCES "Movie"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dd9b27f1..4349ceff 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 } @@ -33,6 +34,7 @@ model Contact { model Movie { id Int @id @default(autoincrement()) screenings Screening[] + reviews Review[] title String runtimeMins Int createdAt DateTime @default(now()) @@ -68,3 +70,14 @@ model Ticket { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model Review { + id Int @id @default(autoincrement()) + customerId Int + movieId Int + customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) + movie Movie @relation(fields: [movieId], references: [id], onDelete: Cascade) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/prisma/seed.js b/prisma/seed.js index 31e28bfa..eca0e37d 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -1,105 +1,150 @@ -const { PrismaClient } = require('@prisma/client'); -const prisma = new PrismaClient(); +const { PrismaClient } = require("@prisma/client") +const prisma = new PrismaClient() async function seed() { - await createCustomer(); - const movies = await createMovies(); - const screens = await createScreens(); - await createScreenings(screens, movies); + await createCustomer() + const movies = await createMovies() + const screens = await createScreens() + await createScreenings(screens, movies) + await creteReviews() - process.exit(0); + process.exit(0) } async function createCustomer() { const customer = await prisma.customer.create({ data: { - name: 'Alice', + name: "Alice", contact: { create: { - email: 'alice@boolean.co.uk', - phone: '1234567890' - } - } + email: "alice@boolean.co.uk", + phone: "1234567890", + }, + }, }, include: { - contact: true - } - }); + contact: true, + }, + }) - console.log('Customer created', customer); + console.log("Customer created", customer) - return customer; + return customer } async function createMovies() { const rawMovies = [ - { title: 'The Matrix', runtimeMins: 120 }, - { title: 'Dodgeball', runtimeMins: 154 }, - ]; + { title: "The Matrix", runtimeMins: 120 }, + { title: "Dodgeball", runtimeMins: 154 }, + ] - const movies = []; + const movies = [] for (const rawMovie of rawMovies) { - const movie = await prisma.movie.create({ data: rawMovie }); - movies.push(movie); + const movie = await prisma.movie.create({ data: rawMovie }) + movies.push(movie) } - console.log('Movies created', movies); + console.log("Movies created", movies) - return movies; + return movies } async function createScreens() { - const rawScreens = [ - { number: 1 }, { number: 2 } - ]; + const rawScreens = [{ number: 1 }, { number: 2 }] - const screens = []; + const screens = [] for (const rawScreen of rawScreens) { const screen = await prisma.screen.create({ - data: rawScreen - }); + data: rawScreen, + }) - console.log('Screen created', screen); + console.log("Screen created", screen) - screens.push(screen); + screens.push(screen) } - return screens; + return screens } async function createScreenings(screens, movies) { - const screeningDate = new Date(); + const screeningDate = new Date() for (const screen of screens) { for (let i = 0; i < movies.length; i++) { - screeningDate.setDate(screeningDate.getDate() + i); + screeningDate.setDate(screeningDate.getDate() + i) const screening = await prisma.screening.create({ data: { startsAt: screeningDate, movie: { connect: { - id: movies[i].id - } + id: movies[i].id, + }, }, screen: { connect: { - id: screen.id - } - } - } - }); + id: screen.id, + }, + }, + }, + }) - console.log('Screening created', screening); + console.log("Screening created", screening) } } } +async function creteReviews() { + const customer = await prisma.customer.create({ + data: { + name: "Leo", + contact: { + create: { + email: "leo@boolean.co.pt", + phone: "1234567890", + }, + }, + }, + include: { + contact: true, + }, + }) + + const movie = await prisma.movie.create({ + data: { + title: "Cars", + runtimeMins: 130, + screenings: { + create: [ + { + screenId: 1, + startsAt: "2024-07-02T17:05:34.644Z", + }, + ], + }, + }, + }) + + const review = await prisma.review.create({ + data: { + customerId: customer.id, + movieId: movie.id, + content: "Great movie!", + }, + include: { + customer: true, + movie: true, + }, + }) + + console.log("Review created", review) +} + seed() - .catch(async e => { - console.error(e); - await prisma.$disconnect(); + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() }) - .finally(() => process.exit(1)); + .finally(() => process.exit(1)) diff --git a/src/controllers/review.js b/src/controllers/review.js new file mode 100644 index 00000000..08d09016 --- /dev/null +++ b/src/controllers/review.js @@ -0,0 +1,62 @@ +const { + getAllReviewsDb, + createReviewDb, + getReviewByIdDb, + updateReviewDb, + deleteReviewDb, +} = require("../domains/review") + +const getAllReviews = async (req, res) => { + const reviews = await getAllReviewsDb() + + res.json({ + reviews, + }) +} + +const creteReview = async (req, res) => { + const { customerId, movieId, content } = req.body + + const review = await createReviewDb(customerId, movieId, content) + + res.status(201).json({ + review, + }) +} + +const getReviewById = async (req, res) => { + const id = Number(req.params.id) + const review = await getReviewByIdDb(id) + + res.json({ + review, + }) +} + +const updateReview = async (req, res) => { + const id = Number(req.params.id) + const { content } = req.body + + const review = await updateReviewDb(id, content) + + res.status(201).json({ + review, + }) +} + +const deleteReview = async (req, res) => { + const id = Number(req.params.id) + const review = await deleteReviewDb(id) + + res.json({ + review, + }) +} + +module.exports = { + getAllReviews, + creteReview, + getReviewById, + updateReview, + deleteReview, +} diff --git a/src/domains/customer.js b/src/domains/customer.js index 504bd0c1..187f436e 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -66,6 +66,13 @@ const updateCostumerDb = async (paramsId, name, contact) => { data: dataClause, include: { contact: true, + reviews: { + select: { + id: true, + content: true, + movie: true + } + } }, }) } diff --git a/src/domains/movie.js b/src/domains/movie.js index bfcea8d5..67cda1a7 100644 --- a/src/domains/movie.js +++ b/src/domains/movie.js @@ -32,6 +32,13 @@ const getAllMoviesDb = async (runtimeLt, runtimeGt) => { where: whereClause, include: { screenings: true, + reviews: { + select: { + id: true, + content: true, + customer: true + } + } }, }) @@ -106,6 +113,13 @@ const getMovieByIdOrTitleDb = async (id, title) => { }, include: { screenings: true, + reviews: { + select: { + id: true, + content: true, + customer: true + } + } }, }) @@ -175,6 +189,13 @@ const updateMovieDb = async (paramsId, title, runtimeMins, screenings) => { data: dataClause, include: { screenings: true, + reviews: { + select: { + id: true, + content: true, + customer: true + } + } }, }) } diff --git a/src/domains/review.js b/src/domains/review.js new file mode 100644 index 00000000..66e64d17 --- /dev/null +++ b/src/domains/review.js @@ -0,0 +1,110 @@ +const BadRequest = require("../errors/BadRequest") +const NotFound = require("../errors/NotFound") +const prisma = require("../utils/prisma") + +const getAllReviewsDb = async () => + await prisma.review.findMany({ + include: { + customer: true, + movie: true, + }, + }) + +const createReviewDb = async (customerId, movieId, content) => { + if (!customerId || !movieId || !content) { + throw new BadRequest("Missing fields in request body") + } + + return await prisma.review.create({ + data: { + customerId: customerId, + movieId: movieId, + content: content, + }, + include: { + customer: true, + movie: true, + }, + }) +} + +const getReviewByIdDb = async (id) => { + const review = await prisma.review.findUnique({ + where: { + id: id, + }, + }) + + if (!review) { + throw new NotFound("Review with that id does not exist") + } + + return await prisma.review.findUnique({ + where: { + id: id, + }, + include: { + customer: true, + movie: true, + }, + }) +} + +const updateReviewDb = async (id, content) => { + const review = await prisma.review.findUnique({ + where: { + id: id, + }, + }) + + if (!review) { + throw new NotFound("Review with that id does not exist") + } + + if (!content) { + throw new BadRequest("Missing fields in request body") + } + + return await prisma.review.update({ + where: { + id: id, + }, + data: { + content: content, + }, + include: { + customer: true, + movie: true, + }, + }) +} + +const deleteReviewDb = async (id) => { + const review = await prisma.review.findUnique({ + where: { + id: id, + }, + }) + + if (!review) { + throw new NotFound("Review with that id does not exist") + } + + return await prisma.review.delete({ + where: { + id: id, + }, + include: { + customer: true, + movie: true, + }, + }) +} + +module.exports = { + getAllReviewsDb, + createReviewDb, + getReviewByIdDb, + updateReviewDb, + deleteReviewDb, +} diff --git a/src/routers/review.js b/src/routers/review.js new file mode 100644 index 00000000..2b220833 --- /dev/null +++ b/src/routers/review.js @@ -0,0 +1,11 @@ +const express = require("express") +const { getAllReviews, creteReview, getReviewById, updateReview, deleteReview } = require("../controllers/review") +const router = express.Router() + +router.get("/", getAllReviews) +router.post("/", creteReview) +router.get("/:id", getReviewById) +router.put("/:id", updateReview) +router.delete("/:id", deleteReview) + +module.exports = router diff --git a/src/server.js b/src/server.js index 8bce7e11..5b72b93c 100644 --- a/src/server.js +++ b/src/server.js @@ -24,11 +24,13 @@ const customerRouter = require("./routers/customer") const movieRouter = require("./routers/movie") const screenRouter = require("./routers/screen") const ticketRouter = require("./routers/ticket") +const reviewRouter = require("./routers/review") app.use("/movies", movieRouter) app.use("/customers", customerRouter) app.use("/screens", screenRouter) app.use("/tickets", ticketRouter) +app.use("/reviews", reviewRouter) app.use((error, req, res, next) => { if (error instanceof BadRequest) { diff --git a/test/setupTests.js b/test/setupTests.js index 2be95321..e4e674ab 100644 --- a/test/setupTests.js +++ b/test/setupTests.js @@ -1,18 +1,13 @@ const prisma = require("../src/utils/prisma") -const deleteTables = () => { - const deleteTables = [ - prisma.ticket.deleteMany(), - prisma.screening.deleteMany(), - prisma.movie.deleteMany(), - prisma.screen.deleteMany(), - prisma.contact.deleteMany(), - 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) +const deleteTables = async () => { + await prisma.review.deleteMany() + await prisma.ticket.deleteMany() + await prisma.screening.deleteMany() + await prisma.movie.deleteMany() + await prisma.screen.deleteMany() + await prisma.contact.deleteMany() + await prisma.customer.deleteMany() } global.beforeAll(() => { From effcdc57ba764400f64936834754c89b3262775e Mon Sep 17 00:00:00 2001 From: Leonardo Lodi Date: Tue, 2 Jul 2024 19:45:29 +0100 Subject: [PATCH 7/7] add tests for review model --- src/controllers/customer.js | 6 +- src/controllers/movie.js | 8 +- src/controllers/review.js | 4 +- src/controllers/screen.js | 2 +- src/controllers/ticket.js | 2 +- src/routers/review.js | 4 +- test/api/extensions/movie-ext.spec.js | 2 +- test/api/extensions/review-ext.spec.js | 143 +++++++++++++++++++++++++ test/helpers/createReview.js | 17 +++ 9 files changed, 174 insertions(+), 14 deletions(-) create mode 100644 test/api/extensions/review-ext.spec.js create mode 100644 test/helpers/createReview.js diff --git a/src/controllers/customer.js b/src/controllers/customer.js index 9a3d07fc..00f3b82c 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -5,10 +5,10 @@ const { const createCustomer = async (req, res) => { const { name, phone, email } = req.body - const createdCustomer = await createCustomerDb(name, phone, email) + const customer = await createCustomerDb(name, phone, email) res.status(201).json({ - customer: createdCustomer, + customer, }) } @@ -18,7 +18,7 @@ const updateCostumer = async (req, res) => { const customer = await updateCostumerDb(paramsId, name, contact) res.status(201).json({ - customer: customer, + customer, }) } diff --git a/src/controllers/movie.js b/src/controllers/movie.js index 6fa40384..52f4a4a4 100644 --- a/src/controllers/movie.js +++ b/src/controllers/movie.js @@ -11,7 +11,7 @@ const getAllMovies = async (req, res) => { const movies = await getAllMoviesDb(runtimeLt, runtimeGt) res.json({ - movies: movies, + movies, }) } @@ -20,7 +20,7 @@ const createMovie = async (req, res) => { const movie = await createMovieDb(title, runtimeMins, screenings) res.status(201).json({ - movie: movie, + movie, }) } @@ -36,7 +36,7 @@ const getMovieByIdOrTitle = async (req, res) => { const movie = await getMovieByIdOrTitleDb(id, title) res.json({ - movie: movie, + movie, }) } @@ -46,7 +46,7 @@ const updateMovie = async (req, res) => { const movie = await updateMovieDb(paramsId, title, runtimeMins, screenings) res.status(201).json({ - movie: movie, + movie, }) } diff --git a/src/controllers/review.js b/src/controllers/review.js index 08d09016..72d321b5 100644 --- a/src/controllers/review.js +++ b/src/controllers/review.js @@ -14,7 +14,7 @@ const getAllReviews = async (req, res) => { }) } -const creteReview = async (req, res) => { +const createReview = async (req, res) => { const { customerId, movieId, content } = req.body const review = await createReviewDb(customerId, movieId, content) @@ -55,7 +55,7 @@ const deleteReview = async (req, res) => { module.exports = { getAllReviews, - creteReview, + createReview, getReviewById, updateReview, deleteReview, diff --git a/src/controllers/screen.js b/src/controllers/screen.js index 581840f5..13fcd683 100644 --- a/src/controllers/screen.js +++ b/src/controllers/screen.js @@ -5,7 +5,7 @@ const createScreen = async (req, res) => { const screen = await createSceenDb(number, screenings) res.status(201).json({ - screen: screen + screen, }) } diff --git a/src/controllers/ticket.js b/src/controllers/ticket.js index c30ec12b..708b6900 100644 --- a/src/controllers/ticket.js +++ b/src/controllers/ticket.js @@ -5,7 +5,7 @@ const createTicket = async (req, res) => { const ticket = await createTicketDb(screeningId, customerId) res.status(201).json({ - ticket: ticket, + ticket, }) } diff --git a/src/routers/review.js b/src/routers/review.js index 2b220833..b78a6929 100644 --- a/src/routers/review.js +++ b/src/routers/review.js @@ -1,9 +1,9 @@ const express = require("express") -const { getAllReviews, creteReview, getReviewById, updateReview, deleteReview } = require("../controllers/review") +const { getAllReviews, createReview, getReviewById, updateReview, deleteReview } = require("../controllers/review") const router = express.Router() router.get("/", getAllReviews) -router.post("/", creteReview) +router.post("/", createReview) router.get("/:id", getReviewById) router.put("/:id", updateReview) router.delete("/:id", deleteReview) diff --git a/test/api/extensions/movie-ext.spec.js b/test/api/extensions/movie-ext.spec.js index b35c10aa..33f5a88a 100644 --- a/test/api/extensions/movie-ext.spec.js +++ b/test/api/extensions/movie-ext.spec.js @@ -75,7 +75,7 @@ describe("Movie Endpoint", () => { }) describe("GET /movies:idOrTitle", () => { - it("can retrieve movie by an id or a title", async () => { + it("can retrieve a movie by an id or a title", async () => { const screen = await createScreen(1) await createMovie("Dodgeball", 120, screen) diff --git a/test/api/extensions/review-ext.spec.js b/test/api/extensions/review-ext.spec.js new file mode 100644 index 00000000..40dd7e0c --- /dev/null +++ b/test/api/extensions/review-ext.spec.js @@ -0,0 +1,143 @@ +const supertest = require("supertest") +const app = require("../../../src/server.js") +const { createCustomer } = require("../../helpers/createCustomer.js") +const { createScreen } = require("../../helpers/createScreen.js") +const { createMovie } = require("../../helpers/createMovie.js") +const createReview = require("../../helpers/createReview.js") + +describe("Review Endpoint", () => { + describe("GET /reviews", () => { + it("can retrieve all reviews from each movie", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + await createReview(customer, movie, "Great movie!") + + const response = await supertest(app).get("/reviews") + + expect(response.status).toEqual(200) + expect(response.body.reviews).not.toEqual(undefined) + expect(response.body.reviews.length).toEqual(1) + expect(response.body.reviews[0].content).toEqual("Great movie!") + }) + }) + + describe("POST /reviews", () => { + it("can create a review for a movie", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + + const request = { + customerId: customer.id, + movieId: movie.id, + content: "Great movie!", + } + + const response = await supertest(app).post("/reviews").send(request) + + expect(response.status).toEqual(201) + expect(response.body.review).not.toEqual(undefined) + expect(response.body.review.content).toEqual("Great movie!") + expect(response.body.review.customer.name).toEqual("John") + expect(response.body.review.movie.title).toEqual("Dodgeball") + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const request = {} + + const response = await supertest(app).post("/reviews").send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + }) + + describe("GET /reviews/:id", () => { + it("can retrieve a review by its id", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + const review = await createReview(customer, movie, "Great movie!") + + const response = await supertest(app).get(`/reviews/${review.id}`) + + expect(response.status).toEqual(200) + expect(response.body.review).not.toEqual(undefined) + expect(response.body.review.content).toEqual("Great movie!") + }) + + it("will return 404 if a review with that id does not exist", async () => { + const response = await supertest(app).get("/reviews/1") + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + }) + + describe("PUT /reviews/:id", () => { + it("can update a review", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + const review = await createReview(customer, movie, "Great movie!") + + const request = { + content: "Not a great movie", + } + + const response = await supertest(app) + .put(`/reviews/${review.id}`) + .send(request) + + expect(response.status).toEqual(201) + expect(response.body.review).not.toEqual(undefined) + expect(response.body.review.content).toEqual("Not a great movie") + }) + + it("will return 400 when there are missing fields in the request body", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + const review = await createReview(customer, movie, "Great movie!") + + const request = {} + + const response = await supertest(app) + .put(`/reviews/${review.id}`) + .send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty("error") + }) + + it("will return 404 if a review with that id does not exist", async () => { + const request = {} + + const response = await supertest(app).put("/reviews/1").send(request) + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + }) + + describe("DELETE /reviews/:id", () => { + it("can delete a review by its id", async () => { + const customer = await createCustomer("John", "123456", "john@test.com") + const screen = await createScreen(1) + const movie = await createMovie("Dodgeball", 120, screen) + const review = await createReview(customer, movie, "Great movie!") + + const response = await supertest(app).delete(`/reviews/${review.id}`) + + expect(response.status).toEqual(200) + }) + + it("will return 404 if a review with that id does not exist", async () => { + const response = await supertest(app).delete("/reviews/1") + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty("error") + }) + }) +}) diff --git a/test/helpers/createReview.js b/test/helpers/createReview.js new file mode 100644 index 00000000..89387d5c --- /dev/null +++ b/test/helpers/createReview.js @@ -0,0 +1,17 @@ +const prisma = require("../../src/utils/prisma") + +const createReview = async (customer, movie, content) => { + return await prisma.review.create({ + data: { + customerId: customer.id, + movieId: movie.id, + content: content, + }, + include: { + customer: true, + movie: true, + }, + }) +} + +module.exports = createReview