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..2db53cdb 100644 --- a/src/controllers/customer.js +++ b/src/controllers/customer.js @@ -1,5 +1,5 @@ const { PrismaClientKnownRequestError } = require("@prisma/client") -const { createCustomerDb } = require('../domains/customer.js') +const { createCustomerDb, updateCustomer, getCustomerByID } = require('../domains/customer.js') const createCustomer = async (req, res) => { const { @@ -43,6 +43,42 @@ const createCustomer = async (req, res) => { } } + +const updateCustomerDetails = async (req, res) => { + const id = Number(req.params.id) + const { name, contact: {phone, email} = {} } = req.body + const foundCustomerId = await getCustomerByID(id) + if (!name) { + return res.status(400).json({ + error: "Customer name field missing" + }) + } + if (!foundCustomerId) { + return res.status(404).json({ + error: "Customer not found with that ID, choose another" + }) + } + try { + const updatedCustomer = await updateCustomer(id, name, phone, email) + + res.status(201).json({ + customer: updatedCustomer + }) + } catch(e) { + if (e instanceof PrismaClientKnownRequestError) { + if(e.code === "P2001") { + return res.status(404).json({ + error: "Customer not found with that ID, choose another" + }) + } + } + res.status(500).json({ + error: e.message + }) + } +} + module.exports = { - createCustomer + createCustomer, + updateCustomerDetails } diff --git a/src/controllers/movie.js b/src/controllers/movie.js new file mode 100644 index 00000000..06f1d575 --- /dev/null +++ b/src/controllers/movie.js @@ -0,0 +1,134 @@ +const { + getAllMovies, + createMovie, + updateMovie, + getAllMoviesByRuntimeLt, + getAllMoviesByRuntimeGt, + getMovieByTitle, + getMovieById +} = require('../domains/movie.js') + +const { PrismaClientKnownRequestError } = require('@prisma/client/runtime/library') + +const getAll = async (req, res) => { + const { runtimeLt,runtimeGt } = req.query + let allMovies + + if(runtimeGt) { + allMovies = await getAllMoviesByRuntimeGt(Number(runtimeGt)) + } + if (runtimeLt) { + allMovies = await getAllMoviesByRuntimeLt(Number(runtimeLt)) + } + allMovies = await getAllMovies() + + res.status(200).json({ + movies: allMovies + }) +} + +const addMovie = async (req, res) => { + const { + title, + runtimeMins + } = req.body + + const titleFound = await getMovieByTitle(title) + + if (!title || !runtimeMins) { + return res.status(400).json({ + error: "Movie title or runtimeMins field missing" + }) + } + if (titleFound) { + return res.status(409).json({ + error: "Movie with that title already exists" + }) + } + try { + const newMovie = await createMovie(title, runtimeMins) + + res.status(201).json({ + movie: newMovie + }) + } catch(e) { + if(e instanceof PrismaClientKnownRequestError) { + if(e.code === "P2002") { + return res.status(409).json({ + error: "Movie with that title already exists" + }) + } + res.status(500).json({ + error: e.message + }) + } + } +} + +const findByID = async (req, res) => { + const id = Number(req.params.id) + const found = await getMovieById(id) + if (!found) { + return res.status(404).json({ + error: "Could not find movie with that ID" + }) + } else { + res.status(200).json({ + movie: found + }) + } +} + +const updateMovieByID = async (req, res) => { + const { + title, + runtimeMins + } = req.body + const id = Number(req.params.id) + const foundId = await getMovieById(id) + const foundTitle = await getMovieByTitle(title) + if (!title || !runtimeMins) { + return res.status(400).json({ + error: "Movie title or runtimeMins field missing" + }) + } + if (!foundId) { + return res.status(404).json({ + error: "Could not find movie with that ID" + }) + } + if (foundTitle) { + return res.status(409).json({ + error: "Movie with that title already exists" + }) + } + try { + const updatedMovie = await updateMovie(id, title, + runtimeMins) + res.status(201).json({ + movie: updatedMovie + }) + } catch(e) { + if (e instanceof PrismaClientKnownRequestError) { + if(e.code === "P2002") { + return res.status(409).json({ + error: "Movie with that title already exists" + }) + } else if (e.code === "P2001") { + return res.status(404).json({ + error: "Could not find movie with that ID" + }) + } + } + res.status(500).json({ + error: e.message + }) + } +} + +module.exports = { + getAll, + addMovie, + findByID, + updateMovieByID +} \ No newline at end of file diff --git a/src/controllers/screen.js b/src/controllers/screen.js new file mode 100644 index 00000000..40cad2f9 --- /dev/null +++ b/src/controllers/screen.js @@ -0,0 +1,36 @@ +const { PrismaClientKnownRequestError } = require("@prisma/client/runtime/library") +const { createScreen, findScreen } = require("../domains/screen") + +const addScreen = async (req, res) => { + const { number } = req.body + const screenFound = await findScreen(number) + if (!number) { + return res.status(400).json({ + error: "Number field missing from screenings" + }) + } + if (screenFound) { + return res.status(409).json({ + error: "This screen already exists please select another screen" + }) + } + try { + const createdScreen = await createScreen(req) + + res.status(201).json({ + screen: createdScreen + }) + } catch(e) { + if(e instanceof PrismaClientKnownRequestError) { + if(e.code === "P2002") { + return res.status(409).json({ + error: "This screen already exists please select another screen" + }) + } + } + } +} + +module.exports = { + addScreen +} \ No newline at end of file diff --git a/src/controllers/ticket.js b/src/controllers/ticket.js new file mode 100644 index 00000000..d6e02681 --- /dev/null +++ b/src/controllers/ticket.js @@ -0,0 +1,45 @@ +const { error } = require("console") +const { getCustomerByID } = require("../domains/customer") +const { findScreeningById } = require("../domains/screen") +const { createTicket } = require("../../test/helpers/createTicket.js") +const { PrismaClientKnownRequestError } = require("@prisma/client/runtime/library") + +const createTicketController = async (req, res) => { + + const {screeningId, customerId} = req.body + const foundScreenId = await findScreeningById(screeningId) + const foundCustomerId = await getCustomerByID(customerId) + + if (!screeningId || !customerId) { + return res.status(400).json({ + error: "Missing fields from the ticket" + }) + } + if (!foundScreenId || !foundCustomerId) { + return res.status(404).json({ + error: "The screen or customer does not exist, please select another ID" + }) + } + try { + const createdTicket = await createTicket(screeningId, customerId) + res.status(201).json({ + ticket: createdTicket + }) + } catch (e) { + if(e instanceof PrismaClientKnownRequestError) { + if(e.code === "P2001") { + return res.status(404).json({ + error: "The screen or customer does not exist, please select another ID" + }) + } + } + res.status(500).json({ + error: e.message + }) + } + +} + +module.exports = { + createTicketController +} \ No newline at end of file diff --git a/src/domains/customer.js b/src/domains/customer.js index c7f315fd..40d073da 100644 --- a/src/domains/customer.js +++ b/src/domains/customer.js @@ -21,6 +21,36 @@ const createCustomerDb = async (name, phone, email) => await prisma.customer.cre } }) + +const updateCustomer = async (id, name, phone, email) => await prisma.customer.update({ + where: { + id: id + }, + data: { + name: name, + contact: { + update: { + phone: phone, + email: email + } + } + }, + include: { + contact: true + } +}) + +const getCustomerByID = async (id) => await prisma.customer.findUnique({ + where: { + id: Number(id) + }, + include: { + contact: true + } +}) + module.exports = { - createCustomerDb + createCustomerDb, + updateCustomer, + getCustomerByID } diff --git a/src/domains/movie.js b/src/domains/movie.js new file mode 100644 index 00000000..92bc11ef --- /dev/null +++ b/src/domains/movie.js @@ -0,0 +1,82 @@ +const prisma = require('../utils/prisma.js') + +const getAllMovies = async () => await prisma.movie.findMany({ + include: { + screenings: true + } +}) + +const createMovie = async (title, + runtimeMins) => await prisma.movie.create({ + data: { + title: title, + runtimeMins: runtimeMins + }, + include: { + screenings: true + } +}) + +const updateMovie = async (id, title, + runtimeMins) => await prisma.movie.update({ + where: { + id: id + }, + data: { + title: title, + runtimeMins: runtimeMins + }, + include: { + screenings: true + } +}) + +const getAllMoviesByRuntimeGt = async (query) => await prisma.movie.findMany({ + where: { + runtimeMins: { + gt: query + } + }, + include: { + screenings: true + } +}) + +const getAllMoviesByRuntimeLt = async (query) => await prisma.movie.findMany({ + where: { + runtimeMins: { + lt: query + } + }, + include: { + screenings: true + } +}) + +const getMovieById = async (id) => await prisma.movie.findUnique({ + where: { + id: id + }, + include: { + screenings: true + } +}) + +const getMovieByTitle = async (title) => await prisma.movie.findFirst({ + where: { + title: title + }, + include: { + screenings: true + } +}) + +module.exports = { + getAllMovies, + createMovie, + updateMovie, + getAllMoviesByRuntimeGt, + getAllMoviesByRuntimeLt, + getMovieById, + getMovieByTitle +} \ No newline at end of file diff --git a/src/domains/screen.js b/src/domains/screen.js new file mode 100644 index 00000000..53af721c --- /dev/null +++ b/src/domains/screen.js @@ -0,0 +1,28 @@ +const prisma = require('../utils/prisma.js') + +const createScreen = async (req) => await prisma.screen.create({ + data: { + number: req.body.number + }, + include: { + screenings: true + } +}) + +const findScreen = async (number) => await prisma.screen.findFirst({ + where: { + number: number + } +}) + +const findScreeningById = async (screeningId) => await prisma.screen.findUnique({ + where: { + id: screeningId + } +}) + +module.exports = { + createScreen, + findScreen, + findScreeningById +} \ No newline at end of file diff --git a/src/errorClasses/index.js b/src/errorClasses/index.js new file mode 100644 index 00000000..fd9c7b52 --- /dev/null +++ b/src/errorClasses/index.js @@ -0,0 +1,26 @@ +class AlreadyExists extends Error { + constructor(message) { + super(message) + this.name = 'AlreadyExists' + } +} + +class MissingFields extends Error { + constructor(message) { + super(message) + this.name = 'MissingFields' + } +} + +class DoesNotExist extends Error { + constructor(message) { + super(message) + this.name = 'DoesNotExist' + } +} + +module.exports = { + AlreadyExists, + MissingFields, + DoesNotExist +} \ No newline at end of file diff --git a/src/routers/customer.js b/src/routers/customer.js index f14a87fc..5288ba3e 100644 --- a/src/routers/customer.js +++ b/src/routers/customer.js @@ -1,7 +1,8 @@ const express = require("express"); const { - createCustomer -} = require('../controllers/customer'); + createCustomer, updateCustomerDetails +} = require('../controllers/customer.js'); + const router = express.Router(); @@ -9,5 +10,6 @@ const router = express.Router(); // 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.put('/:id', updateCustomerDetails) module.exports = router; diff --git a/src/routers/movie.js b/src/routers/movie.js new file mode 100644 index 00000000..8b3eeda5 --- /dev/null +++ b/src/routers/movie.js @@ -0,0 +1,11 @@ +const express = require('express') +const { getAll, addMovie, findByID, updateMovieByID } = require('../controllers/movie.js') + +const router = express.Router() + +router.get('/', getAll) +router.post('/', addMovie) +router.get('/:id', findByID) +router.put('/:id', updateMovieByID) + +module.exports = router \ No newline at end of file diff --git a/src/routers/screen.js b/src/routers/screen.js new file mode 100644 index 00000000..536eb488 --- /dev/null +++ b/src/routers/screen.js @@ -0,0 +1,7 @@ +const express = require("express") +const { addScreen } = require("../controllers/screen") +const router = express.Router() + +router.post('/', addScreen) + +module.exports = router \ No newline at end of file diff --git a/src/routers/ticket.js b/src/routers/ticket.js new file mode 100644 index 00000000..85345ab1 --- /dev/null +++ b/src/routers/ticket.js @@ -0,0 +1,7 @@ +const express = require('express') +const { createTicketController } = require('../controllers/ticket') +const router = express.Router() + +router.post('/', createTicketController) + +module.exports = router \ No newline at end of file diff --git a/src/server.js b/src/server.js index 93d47a16..715af555 100644 --- a/src/server.js +++ b/src/server.js @@ -17,5 +17,15 @@ app.use(express.urlencoded({ extended: true })); const customerRouter = require('./routers/customer'); app.use('/customers', customerRouter); +const movieRouter = require('./routers/movie') +app.use('/movies', movieRouter) + +const screenRouter = require('./routers/screen') +app.use('/screens', screenRouter) + +const ticketRouter = require('./routers/ticket') +app.use('/tickets', ticketRouter) + + module.exports = app diff --git a/test/api/extensions/movies-ext.spec.js b/test/api/extensions/movies-ext.spec.js new file mode 100644 index 00000000..7d471959 --- /dev/null +++ b/test/api/extensions/movies-ext.spec.js @@ -0,0 +1,89 @@ +const supertest = require('supertest') +const app = require('../../../src/server.js') +const { createMovie } = require('../../helpers/createMovie.js') +const { describe } = require('node:test') +const { title } = require('process') + +describe('Movie Endpoit', () => { + describe('PUT /movies/', () => { + it('will return 400 if there are missing fields in the request body', async () => { + const movie = await createMovie('The Man Who Laughs', 110) + + 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 the movie is not found', async () => { + const request = { + title: 'Raging Bull', + runtimeMins: 129 + } + + const response = await supertest(app) + .put(`/movies/10000`) + .send(request) + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty('error') + }) + it('will return 409 if a movie already exists with the same title', async () => { + const movie = await createMovie('To Kill A Mockingbird', 129) + + const request = { + title: 'To Kill A Mockingbird', + runtimeMins: 129 + } + const response = await supertest(app) + .put(`/movies/${movie.id}`) + .send(request) + + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty('error') + }) + }) + describe('GET /movies/id', () => { + it('will return 404 if the movie is not found', async () => { + await createMovie('Gangs of New York', 167) + await createMovie('Raging Bull', 129) + + const response = await supertest(app) + .get(`/movies/10000`) + + expect(response.status).toEqual(404) + expect(response.body).toHaveProperty('error') + }) + }) + describe('POST /movies/', () => { + it('will return 400 if the movies fields is missing a 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 the movie title already exists in the db', async() => { + const movie = await (createMovie('Psycho', 109)) + + const request = { + title: 'Psycho', + runtimeMins: 109 + } + + const response = await supertest(app) + .post(`/movies/`) + .send(request) + + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty('error') + }) + }) +}) \ No newline at end of file diff --git a/test/api/extensions/screen-ext.spec.js b/test/api/extensions/screen-ext.spec.js new file mode 100644 index 00000000..3d481c7f --- /dev/null +++ b/test/api/extensions/screen-ext.spec.js @@ -0,0 +1,36 @@ +const supertest = require('supertest') +const app = require('../../../src/server.js') +const { createScreen } = require('../../helpers/createScreen.js') + +const { describe } = require('node:test') + +describe('Screen Endpoint', () => { + describe('POST /screens/', () => { + it('will return 400 if there is a missing field in the request body', async () => { + const screen = await createScreen(1) + + const request = {} + + const response = await supertest(app) + .post(`/screens/`) + .send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty('error') + }) + it('will return 409 if a screen already exists with this number', async () => { + const screen = await createScreen(1) + + const request = { + number: 1 + } + + const response = await supertest(app) + .post(`/screens/`) + .send(request) + + expect(response.status).toEqual(409) + expect(response.body).toHaveProperty('error') + }) + }) +}) \ No newline at end of file diff --git a/test/api/extensions/ticket-ext.spec.js b/test/api/extensions/ticket-ext.spec.js new file mode 100644 index 00000000..b6803aa9 --- /dev/null +++ b/test/api/extensions/ticket-ext.spec.js @@ -0,0 +1,48 @@ +const supertest = require('supertest') +const app = require('../../../src/server.js') +const { createTicket } = require('../../helpers/createTicket.js') + +const { describe } = require('node:test') + +describe('Ticket Endpoint', () => { + describe('POST /ticket/', () => { + it('will create a ticket for the customer', () => { + async () => { + + const request = { + screeningId: 1, + customerId: 1 + } + + const response = await supertest(app) + .post('/screens/') + .send(request) + + expect(response.status).toEqual(201) + expect(response.body.customer).not.toEqual(undefined) + expect(response.body.screening).not.toEqual(undefined) + expect(response.body.movie.title).toEqual('The Matrix') + expect(response.body.customer.name).toEqual('Tom') + } + }) + it('will return 400 if the screenId or customerId fields are missing', async () => { + const request = {} + + const response = await supertest(app) + .post('/screens/') + .send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty('error') + }) + it('will return 400 if the screeningId or customerId is not found', async () => { + const request = {} + const response = await supertest(app) + .post('/screens/') + .send(request) + + expect(response.status).toEqual(400) + expect(response.body).toHaveProperty('error') + }) + }) +}) \ No newline at end of file diff --git a/test/api/routes/movies.spec.js b/test/api/routes/movies.spec.js index 209c4bc3..cb5efa7c 100644 --- a/test/api/routes/movies.spec.js +++ b/test/api/routes/movies.spec.js @@ -3,11 +3,12 @@ const app = require("../../../src/server.js") 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('Superbad', 120, screen) await createMovie('Scream', 113, screen) const response = await supertest(app).get('/movies') @@ -17,7 +18,7 @@ describe("Movies Endpoint", () => { expect(response.body.movies.length).toEqual(2) const [movie1, movie2] = response.body.movies - expect(movie1.title).toEqual('Dodgeball') + expect(movie1.title).toEqual('Superbad') expect(movie1.runtimeMins).toEqual(120) expect(movie1.screenings).not.toEqual(undefined) expect(movie1.screenings.length).toEqual(1) @@ -32,17 +33,17 @@ describe("Movies Endpoint", () => { describe("POST /movies", () => { it("will create a movie", async () => { const request = { - title: "Top Gun", + title: "Full Metal Jacket", 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.title).toEqual('Full Metal Jacket') expect(response.body.movie.runtimeMins).toEqual(110) expect(response.body.movie.screenings).not.toEqual(undefined) expect(response.body.movie.screenings.length).toEqual(0) @@ -52,14 +53,14 @@ describe("Movies Endpoint", () => { 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 created = await createMovie('Lucky', 89, 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.title).toEqual('Lucky') + expect(response.body.movie.runtimeMins).toEqual(89) expect(response.body.movie.screenings).not.toEqual(undefined) expect(response.body.movie.screenings.length).toEqual(1) }) @@ -68,20 +69,20 @@ describe("Movies Endpoint", () => { 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 created = await createMovie('King of Comedy', 118, screen) const request = { - title: 'Scream', + title: 'Scary Movie', 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.title).toEqual('Scary Movie') 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..037ebf70 100644 --- a/test/api/routes/screens.spec.js +++ b/test/api/routes/screens.spec.js @@ -5,7 +5,7 @@ describe("Screens Endpoint", () => { describe("POST /screens", () => { it("will create a new screen", async () => { const request = { - number: 10 + number: 11 } const response = await supertest(app) @@ -14,7 +14,7 @@ describe("Screens Endpoint", () => { expect(response.status).toEqual(201) expect(response.body.screen).not.toEqual(undefined) - expect(response.body.screen.number).toEqual(10) + expect(response.body.screen.number).toEqual(11) }) }) }) \ No newline at end of file diff --git a/test/helpers/createTicket.js b/test/helpers/createTicket.js new file mode 100644 index 00000000..b2d930bf --- /dev/null +++ b/test/helpers/createTicket.js @@ -0,0 +1,18 @@ +const prisma = require("../../src/utils/prisma") + +const createTicket = async (screeningId, customerId) => await prisma.ticket.create({ + where: { + screeningId: screeningId, + customerId: customerId + }, + include: { + screening: true, + customer: true, + movie, + screen + } +}) + +module.exports = { + createTicket +} \ No newline at end of file