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/.gitignore b/.gitignore index 586e2f80..e670051d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules # Keep environment variables out of version control .env ._* +.vscode \ No newline at end of file diff --git a/README.md b/README.md index bcdd1da3..3e1efa7f 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,70 @@ # Cinema Booking API -> API stands for Application Programming Interface, which is a set of definitions and protocols for building and integrating software. Simply put, they allow two pieces of software to communicate with each other through a pre-defined interface. +## Introduction -## Learning Objectives +This project demonstrates my skill to create a working RESTful API using PostgreSQL and Prisma. The API allows interaction with a cinema booking system database and can be tested using tools like Postman. -- Design and build a database-backend API +## Installation -## Introduction +1. Clone the repository: + + ```bash + git clone https://github.com/AtikoSpeed/database-cinema-booking-api + ``` + +2. Navigate to the project directory: + + ```bash + cd database-cinema-booking-api + ``` -So far, we've designed a database structure for a cinema booking system and implemented that design using an ORM. The next step is to build an API that allows the cinema and its customers to interact with the database. +3. Install dependencies: -Frontend applications usually won't interact with a database directly, especially if it's a web app; they'll instead send instructions to a server which will process those instructions and give back a response. We decide how the server receives instructions, processes them and responds back by creating an API. + ```bash + npm ci + ``` -## Setting up +4. I've included my own .env file for demonstration purposes. **This is temporary and will be deleted** -The full database schema and seed file for this exercise has already been implemented, your focus is on building the API itself. +5. Reset Prisma migrations and set up the database schema: -**Note: Although we need to create a new primary database, we can reuse a shadow database across multiple projects since Prisma resets it after using it.** + ```bash + npx prisma migrate reset --force + ``` -1. Rename the `.env.example` file to `.env` -2. Edit the `DATABASE_URL` variable in `.env`, swapping `YOUR_DATABASE_URL` for the URL of your database -3. Create another separate **TEST** database instance; you may need to use a different cloud provider as some only allow one instance per free account. [Supabase](https://supabase.com/) may be a good option -4. Edit the `TEST_DATABASE_URL` variable in `.env`, swapping `YOUR_TEST_DB_URL` for the URL of the separate **TEST** database instance you just created -7. Run `npm ci` to install the project dependencies. -8. Run `npx prisma migrate reset` to execute the existing migrations & data seed. Press `y` when it asks if you're sure. +## Usage -## Instructions +Start the server: -- Run the app with `npm start` -- Work through each route detailed in the [API Spec](https://boolean-uk.github.io/database-cinema-booking-api/standard). +```bash +npm start +``` -## Extensions +## Testing with Postman -- Work through each route detailed in the [extended API Spec](https://boolean-uk.github.io/database-cinema-booking-api/extensions). This will require making changes to your existing routes, creating error responses for certain situations, and introducing a new route. -- You will need to create your own tests for these endpoints. Use the existing test provided in `test/api/extensions` as a guide. +Use Postman or any other API client to interact with the API endpoints. The available endpoints are: -## Extensions to the Extensions +### Movies -- Change your movie list GET route to only respond with movies that have a future screening time -- Add the ability for customers to leave reviews on movies - - This will require a new entity in your diagram, schema file and seed file. Remember the `npx prisma generate`, `npx prisma migrate dev --create-only --skip-seed --name reviews` and `npx prisma migrate reset` commands from an earlier exercise! -- You will need to create your own tests for these endpoints. Use the existing test provided in `test/api/extensions` as a guide. - - If you create the new `Review` entity, then you will also need to re-run `npm run test:migration` so that your test database runs the migration to create this table. Do this AFTER you have created the new migration file. +- `GET /movies` - Retrieve a list of movies along with their screenings. +- `GET /movies/{id}` - Retrieve a specific movie by ID along with its screenings. +- `POST /movies` - Create a new movie. +- `PUT /movies/{id}` - Update an existing movie. +- `DELETE /movies/{id}` - Delete a movie. -## Testing your work +### Customers -- First, make sure you have created / setup the test database instance and env var, as described in the "Setting Up" section. -- Next, run the command `npm run test:migration` - this will run the schema migrations against the test database. **You only need to do this the one time.** +- `POST /customers/register` - Register a new customer. +- `PUT /customers/{id}` - Update a customer's information. -Now, whenever you want to run tests locally: -- Run the test suite with `npm test` for core requirements. -- Run the extension test suite with `npm run test-extensions`. - - When working on extensions, create your own tests by using the one provided in `test/api/extensions` as a guide. +### Screens -So far, you may have been using `curl` to manually test your API endpoints. You can continue to use this approach if you choose, or you can download a tool to make things a little easier. There are two main options: +- `POST /screens` - Create a new screen. -- [Insomnia](https://insomnia.rest/download) (recommended) -- [Postman](https://www.postman.com/) +## Technologies Used -These tools are quite similar to your web browser but have some additional functionality to make structuring API requests easier. +- Node.js +- Express.js +- PostgreSQL +- Prisma ORM +- Postman (for testing) diff --git a/package-lock.json b/package-lock.json index 0fc727e5..6d38ea40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "^5.16.1", + "@prisma/client": "^5.19.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", @@ -18,7 +18,7 @@ "devDependencies": { "jest": "^29.7.0", "nodemon": "^3.0.2", - "prisma": "^5.16.1", + "prisma": "^5.19.0", "supertest": "^6.3.3" } }, @@ -1009,10 +1009,11 @@ } }, "node_modules/@prisma/client": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.16.1.tgz", - "integrity": "sha512-wM9SKQjF0qLxdnOZIVAIMKiz6Hu7vDt4FFAih85K1dk/Rr2mdahy6d3QP41K62N9O0DJJA//gUDA3Mp49xsKIg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.19.0.tgz", + "integrity": "sha512-CzOpau+q1kEWQyoQMvlnXIHqPvwmWbh48xZ4n8KWbAql0p8PC0BIgSTYW5ncxXa4JSEff0tcoxSZB874wDstdg==", "hasInstallScript": true, + "license": "Apache-2.0", "engines": { "node": ">=16.13" }, @@ -1026,48 +1027,53 @@ } }, "node_modules/@prisma/debug": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.16.1.tgz", - "integrity": "sha512-JsNgZAg6BD9RInLSrg7ZYzo11N7cVvYArq3fHGSD89HSgtN0VDdjV6bib7YddbcO6snzjchTiLfjeTqBjtArVQ==", - "devOptional": true + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.19.0.tgz", + "integrity": "sha512-+b/G0ubAZlrS+JSiDhXnYV5DF/aTJ3pinktkiV/L4TtLRLZO6SVGyFELgxBsicCTWJ2ZMu5vEV/jTtYCdjFTRA==", + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.16.1.tgz", - "integrity": "sha512-KkyF3eIUtBIyp5A/rJHCtwQO18OjpGgx18PzjyGcJDY/+vNgaVyuVd+TgwBgeq6NLdd1XMwRCI+58vinHsAdfA==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.19.0.tgz", + "integrity": "sha512-UtW+0m4HYoRSSR3LoDGKF3Ud4BSMWYlLEt4slTnuP1mI+vrV3zaDoiAPmejdAT76vCN5UqnWURbkXxf66nSylQ==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.16.1", - "@prisma/engines-version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303", - "@prisma/fetch-engine": "5.16.1", - "@prisma/get-platform": "5.16.1" + "@prisma/debug": "5.19.0", + "@prisma/engines-version": "5.19.0-31.5fe21811a6ba0b952a3bc71400666511fe3b902f", + "@prisma/fetch-engine": "5.19.0", + "@prisma/get-platform": "5.19.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303.tgz", - "integrity": "sha512-HkT2WbfmFZ9WUPyuJHhkiADxazHg8Y4gByrTSVeb3OikP6tjQ7txtSUGu9OBOBH0C13dPKN2qqH12xKtHu/Hiw==", - "devOptional": true + "version": "5.19.0-31.5fe21811a6ba0b952a3bc71400666511fe3b902f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.19.0-31.5fe21811a6ba0b952a3bc71400666511fe3b902f.tgz", + "integrity": "sha512-GimI9aZIFy/yvvR11KfXRn3pliFn1QAkdebVlsXlnoh5uk0YhLblVmeYiHfsu+wDA7BeKqYT4sFfzg8mutzuWw==", + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.16.1.tgz", - "integrity": "sha512-oOkjaPU1lhcA/Rvr4GVfd1NLJBwExgNBE36Ueq7dr71kTMwy++a3U3oLd2ZwrV9dj9xoP6LjCcky799D9nEt4w==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.19.0.tgz", + "integrity": "sha512-oOiPNtmJX0cP/ebu7BBEouJvCw8T84/MFD/Hf2zlqjxkK4ojl38bB9i9J5LAxotL6WlYVThKdxc7HqoWnPOhqQ==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.16.1", - "@prisma/engines-version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303", - "@prisma/get-platform": "5.16.1" + "@prisma/debug": "5.19.0", + "@prisma/engines-version": "5.19.0-31.5fe21811a6ba0b952a3bc71400666511fe3b902f", + "@prisma/get-platform": "5.19.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.16.1.tgz", - "integrity": "sha512-R4IKnWnMkR2nUAbU5gjrPehdQYUUd7RENFD2/D+xXTNhcqczp0N+WEGQ3ViyI3+6mtVcjjNIMdnUTNyu3GxIgA==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.19.0.tgz", + "integrity": "sha512-s9DWkZKnuP4Y8uy6yZfvqQ/9X3/+2KYf3IZUVZz5OstJdGBJrBlbmIuMl81917wp5TuK/1k2TpHNCEdpYLPKmg==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.16.1" + "@prisma/debug": "5.19.0" } }, "node_modules/@sinclair/typebox": { @@ -1433,12 +1439,13 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -1446,7 +1453,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -1459,6 +1466,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -1466,12 +1474,14 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/body-parser/node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -1493,12 +1503,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1555,6 +1566,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1773,6 +1785,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1784,9 +1797,10 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2106,16 +2120,17 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -2195,10 +2210,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2529,6 +2545,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -2667,6 +2684,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3531,6 +3549,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3555,12 +3574,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4048,19 +4068,23 @@ } }, "node_modules/prisma": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.16.1.tgz", - "integrity": "sha512-Z1Uqodk44diztImxALgJJfNl2Uisl9xDRvqybMKEBYJLNKNhDfAHf+ZIJbZyYiBhLMbKU9cYGdDVG5IIXEnL2Q==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.19.0.tgz", + "integrity": "sha512-Pu7lUKpVyTx8cVwM26dYh8NdvMOkMnJXzE8L6cikFuR4JwyMU5NKofQkWyxJKlTT4fNjmcnibTvklV8oVMrn+g==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.16.1" + "@prisma/engines": "5.19.0" }, "bin": { "prisma": "build/index.js" }, "engines": { "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" } }, "node_modules/prompts": { @@ -4134,9 +4158,10 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -4243,7 +4268,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -4678,6 +4704,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -4730,6 +4757,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" diff --git a/package.json b/package.json index ebaf03be..4793339a 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,11 @@ "devDependencies": { "jest": "^29.7.0", "nodemon": "^3.0.2", - "prisma": "^5.16.1", + "prisma": "^5.19.0", "supertest": "^6.3.3" }, "dependencies": { - "@prisma/client": "^5.16.1", + "@prisma/client": "^5.19.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", diff --git a/prisma/migrations/20240827155623_bruhmoment/migration.sql b/prisma/migrations/20240827155623_bruhmoment/migration.sql new file mode 100644 index 00000000..af5102c8 --- /dev/null +++ b/prisma/migrations/20240827155623_bruhmoment/migration.sql @@ -0,0 +1 @@ +-- This is an empty migration. \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dd9b27f1..8eafddc7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,28 +6,29 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") } // https://www.prisma.io/docs/concepts/components/prisma-schema/data-model model Customer { - id Int @id @default(autoincrement()) - name String - contact Contact? - tickets Ticket[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + name String + contact Contact? + tickets Ticket[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Contact { - id Int @id @default(autoincrement()) - customer Customer @relation(fields: [customerId], references: [id]) - customerId Int @unique - phone String - email String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + customer Customer @relation(fields: [customerId], references: [id]) + customerId Int @unique + phone String + email String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Movie { @@ -40,31 +41,31 @@ model Movie { } model Screen { - id Int @id @default(autoincrement()) - number Int - screenings Screening[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + number Int + screenings Screening[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Screening { - id Int @id @default(autoincrement()) - tickets Ticket[] - movie Movie @relation(fields: [movieId], references: [id]) - movieId Int - screen Screen @relation(fields: [screenId], references: [id]) - screenId Int - startsAt DateTime - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + tickets Ticket[] + movie Movie @relation(fields: [movieId], references: [id]) + movieId Int + screen Screen @relation(fields: [screenId], references: [id]) + screenId Int + startsAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Ticket { - id Int @id @default(autoincrement()) - screening Screening @relation(fields: [screeningId], references: [id]) - screeningId Int - customer Customer @relation(fields: [customerId], references: [id]) - customerId Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + screening Screening @relation(fields: [screeningId], references: [id]) + screeningId Int + customer Customer @relation(fields: [customerId], references: [id]) + customerId Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } diff --git a/src/routers/customer.js b/src/routers/customer.js deleted file mode 100644 index f14a87fc..00000000 --- a/src/routers/customer.js +++ /dev/null @@ -1,13 +0,0 @@ -const express = require("express"); -const { - createCustomer -} = require('../controllers/customer'); - -const router = express.Router(); - -// In index.js, we told express that the /customer route should use this router file -// The below /register route extends that, so the end result will be a URL -// that looks like http://localhost:4040/customer/register -router.post("/register", createCustomer); - -module.exports = router; diff --git a/src/routers/customers.js b/src/routers/customers.js new file mode 100644 index 00000000..b2e757dd --- /dev/null +++ b/src/routers/customers.js @@ -0,0 +1,33 @@ +const express = require("express"); +const { createCustomer } = require("../controllers/customer"); +const prisma = require("../utils/prisma"); + +const router = express.Router(); + +// In index.js, we told express that the /customer route should use this router file +// The below /register route extends that, so the end result will be a URL +// that looks like http://localhost:4040/customer/register + +// POST REQUESTS + +router.post("/register", createCustomer); + +// PUT REQUESTS + +router.put("/:id", async (req, res) => { + const customer = await prisma.customer.update({ + where: { + id: Number(req.params.id), + }, + data: { + name: req.body.name, + }, + include: { + contact: true, + }, + }); + + res.status(201).json({ customer }); +}); + +module.exports = router; diff --git a/src/routers/movies.js b/src/routers/movies.js new file mode 100644 index 00000000..d6184582 --- /dev/null +++ b/src/routers/movies.js @@ -0,0 +1,76 @@ +const express = require("express"); +const prisma = require("../utils/prisma.js"); +const { response } = require("../server.js"); + +const router = express.Router(); + +// GET REQUESTS + +router.get("/", async (req, res) => { + // GET ALL THE MOVIES AND THEIR SCREENINGS + + const response = await prisma.movie.findMany({ + include: { screenings: true }, + }); // GET ALL MOVIES + + res.json({ movies: response }); +}); + +router.get("/:id", async (req, res) => { + try { + // TRY TO RUN THE ASYNC AWAIT FUNCTION + const [movie] = await prisma.movie.findMany({ + // GET MOVIE WITH GIVEN ID + where: { + id: Number(req.params.id), + }, + include: { screenings: true }, + }); + + res.json({ movie }); + } catch (error) { + // IF THERE ARE NO MOVIES THE AWAIT WILL THROW AN ERROR. THIS WILL CATCH IT + res.status(404).json({ error: "MOVIE NOT FOUND" }); + } +}); + +// POST REQUESTS + +router.post("/", async (req, res) => { + const movie = await prisma.movie.create({ + // CREATES A NEW MOVIE WITH THE GIVEN TITLE AND RUNTIME IN THE REQUEST BODY + data: { + title: req.body.title, + runtimeMins: req.body.runtimeMins, + }, + include: { screenings: true }, + }); + + res.status(201).json({ movie: movie }); +}); + +// PUT REQUESTS + +router.put("/:id", async (req, res) => { + try { + // TRY TO RUN THE ASYNC AWAIT FUNCTION + const movie = await prisma.movie.update({ + // UPDATE MOVIE WITH GIVEN ID + where: { + id: Number(req.params.id), + }, + data: { + title: req.body.title, + runtimeMins: req.body.runtimeMins, + }, + include: { screenings: true }, + }); + + res.status(201).json({ movie }); + } catch (error) { + // IF THERE ARE NO MOVIES THE AWAIT WILL THROW AN ERROR. THIS WILL CATCH IT + res.status(404).json({ error: "MOVIE NOT FOUND" }); + } +}); + +module.exports = router; diff --git a/src/routers/screens.js b/src/routers/screens.js new file mode 100644 index 00000000..7eab53c0 --- /dev/null +++ b/src/routers/screens.js @@ -0,0 +1,19 @@ +const express = require("express"); +const prisma = require("../utils/prisma.js"); +const { response } = require("../server.js"); + +const router = express.Router(); + +// THE SOLE POST REQUEST + +router.post("/", async (req, res) => { + const screen = await prisma.screen.create({ + data: { + number: Number(req.body.number), + }, + }); + + res.status(201).json({ screen }); +}); + +module.exports = router; diff --git a/src/server.js b/src/server.js index 93d47a16..3a14319d 100644 --- a/src/server.js +++ b/src/server.js @@ -1,21 +1,24 @@ -const express = require('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(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 customersRouter = require("./routers/customers"); +const moviesRouter = require("./routers/movies"); +const screensRouter = require("./routers/screens"); +const prisma = require("./utils/prisma"); +app.use("/customers", customersRouter); +app.use("/movies", moviesRouter); +app.use("/screens", screensRouter); -module.exports = app +module.exports = app;