diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..74fbfa0d887ee53aa9dd812cd34a4239e723e69f --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# FWE-SS-23-769544 - Advanced web technologies + +## Set up the project ("beschreibt, wie die Applikation aufzusetzen ist") +0. You will need beforehand +- Node: Webserver (https://nodejs.org/en 18.16.0) +- Postman: For API / HTTP calls & checks (https://www.postman.com/downloads/) +- Git clone this repository to your machine and move a cmd inside + - git clone https://code.fbi.h-da.de/stsezill/FWE-SS-23-769544.git + + +1. <b>Set up the backend</b>: Please follow the detailed instructions within the readme of the backend/ folder +2. <b>Set up the frontend</b>: Please follow the detailed instructions within the readme of the frontend/ folder diff --git a/backend/.gitignore b/backend/.gitignore index 11ddd8dbe662a68e189c3a66ab6fc2df45c555bb..a554ede7c816715159859563ffe4a61f5014ef0e 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,5 @@ node_modules +dist + # Keep environment variables out of version control .env diff --git a/backend/_old-Dockerfile b/backend/Dockerfile similarity index 100% rename from backend/_old-Dockerfile rename to backend/Dockerfile diff --git a/backend/README.md b/backend/README.md index 0fc33b2bba54f12929742dac2e557492057090e6..f6aac9a1fcc6f24021f9585b15511f942c716989 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # FWE-SS-23-769544 - BACKEND -## Set up the project (beschreibt, wie die Applikation aufzusetzen ist) +## Set up the project ("beschreibt, wie die Applikation aufzusetzen ist") 0. You will need beforehand - Node: Webserver (https://nodejs.org/en 18.16.0) - Postman: For API / HTTP calls & checks (https://www.postman.com/downloads/) @@ -8,150 +8,156 @@ - git clone https://code.fbi.h-da.de/stsezill/FWE-SS-23-769544.git -1. Run docker container with postgres -- (docker-compose down) -- (docker system prune -a --volumes) -- docker-compose up -d - -2. Move to backend/ directory -- cd backend/ - -3. Usually not in repository: Set up .env file in backend/.env with following two lines (without "- ") +1. <b>Run docker container with postgres</b> +- `(docker-compose down)` +- `(docker system prune -a --volumes)` +- `docker-compose up -d` +<br> +2. <b>Move to this backend/ directory<br>(all following commands etc. happen here in the backend module / directory)</b> +- `cd backend/` +<br> +3. <b>Usually not in repository: Set up .env file in backend/.env with following two lines (without "- ")</b> - DATABASE_URL="postgresql://sebastian:fweSS22@localhost:5432/chefkochDB?schema=public" - MY_JWT_SECRET = "MY_SECRET_SECRET" - -4. Install node modules from package.json -- npm i - -5. Create Prisma DB -- npx prisma db push --schema=src/prisma/schema.prisma - -6. Run node on localhost:3000 +<br> +4. <b>Install node modules from package.json</b> +- `npm i` +<br> +5. <b>Create Prisma DB</b> +- `npx prisma db push --schema=src/prisma/schema.prisma` +<br> +6. <b>Run node on localhost:3000</b> Note: since these are permanent containers i suggest to use multiple vs terminals -- npm run start:dev - -7. Run Prisma Studio on localhost:5555 -- npm run start:prisma - -8. Create fresh DB Schema -- npm run schema:fresh - -### Now the project is set up and you have a node backend running on localhost:3000 and a database interface on localhost:5555 to look at the data +- `npm run start:dev` +<br> +7. <b>Run Prisma Studio on localhost:5555</b> +- `npm run start:prisma` +<br> +8. <b>Create fresh DB Schema</b> +- `npm run schema:fresh` + +### Now the project is set up and you have:<br>- a node backend running on <i>localhost:3000</i><br>- a database interface on <i>localhost:5555</i> ===========================================================================<br> -## Test this project (beschreibt wie die API getestet werden kann.) -To test the application, there is a postman collection you can click through and also check out prismas localhost:5555 to view the effect on the Database live! +## Test this project ("beschreibt wie die API getestet werden kann") +To test the application, there is a postman collection you can click through. You can also check out prismas <i>localhost:5555</i> to view the effect on the Database live! 1. Open Postman +<br> 2. Click on the "Hamburger"-Icon on the very top left "File->Import..." +<br> 3. Drag & Drop the /backend/testing.postman_collection-v2.1.json into the Window - Shouldn´t be a problem, but if it does not work please also try the /backend/testing.postman_collection-v2.0.json +<br> 4. Click on left->collections and open the imported "Integration testing" - Since as a bonus I included the authorization token from the classes you first <b>must</b> run "0-Register User" and "1-Login User". If you want you can take a look at an unauthorized access with "2-Unauthorized-no-jwt-token" - +<br> 5. Now move through the folders 1-Recipe, 2-Ingredient, 3-CookingStep, 4-CustomRoutes, 5-ForeignkeyHandling from top to bottom and view the localhost:5555 for the database structure throughout the testing of hopefully all necessary cases. - -Example:<br> +<br> +Example Video ("Testingexample-Postman-Auth+Recipe.mp4"):<br> <video width="320" height="240" controls> <source src="Testingexample-Postman-Auth+Recipe.mp4" type="video/mp4"> - Your browser does not support the video tag. + Can´t load, please look at the video "Testingexample-Postman-Auth+Recipe.mp4" from the file structure </video> ===========================================================================<br> ## Usage (die Funktionalitäten beschreibt) The contents inside this backend/ folder describe a REST API written in Node.js with Typescript and Express, handeling Data in a Database described here: -<img style="float: right;" src="DB-draft.png"><br> +<img style="float: right;" src="DB-draft.png"><br><br> With this API you can view (GET), create (POST), update (PUT) and delete (DELETE) data within this database via the Prisma ORM functionality implemented within this folders code. To do that you call different routes as follows in the next paragraph more in detail. To achieve that we have following functionalities / folders, all located in /backend/src/. -Preface: The given folders and containing files each have the according functions -- entities/: Give a file structure including a node_module validator (yup) to use as schemas in the other files -- prisma/: Give interface functions to interact asynchronously with the database by selecting, updating, inserting or deleting according data -- controller/: Strongly bound to the prisma DB Interface for the regarding entity. Checks user Input and calls DB actions depending on the route it catches (get, post, put, delete) -- middleware/: just for the authentication process - injects itself between controller / routers and checks if the user - jwt token is valid for usage, hence denies or grants access -- prisma/prismaClient.ts: Instance of PrismaClient, called and used within the DBInterfaces to communicate with the DB asynchronously. +<b>Preface</b>: The given folders and containing files each have the according functions +- <b>entities/</b>: Give a file structure including a node_module validator (yup) to use as schemas in the other files +- <b>prisma/</b>: Give interface functions to interact asynchronously with the database by selecting, updating, inserting or deleting according data + - <b>prisma/prismaClient.ts</b>: Instance of PrismaClient, called and used within the DBInterfaces to communicate with the DB asynchronously. +- <b>controller/</b>: Strongly bound to the prisma DB Interface for the regarding entity. Checks user Input and calls DB actions depending on CRUD, it is called with (get, post, put, delete) +- <b>middleware/</b>: just for the authentication process - injects itself between controller / routers and checks if the user - jwt token is valid for usage, hence denies or grants access + ==========<br><br> -- DB initialization: Is done over the ./prisma/schema.prisma file with the prisma node_module -- User and Authorization: Is done over entities/UserEntity.ts, prisma/userDBInterface.ts, controller/authController.ts, middleware/auth.ts -- Recipe Selection, Creation, Updating and Deletion: Is done over entities/RecipeEntity.ts, prisma/recipeDBInterface.ts, controller/recipeEntriesController.ts -- Ingerdient Selection, Creation, Updating and Deletion: Is done over entities/IngredientEntity.ts, prisma/ingredientDBInterface.ts, controller/ingredientEntriesController.ts -- CookingSteps Selection, Creation, Updating and Deletion: Is done over entities/CookingStepEntity.ts, prisma/cookingStepDBInterface.ts, controller/cookingStepEntriesController.ts +- <b>DB initialization</b>: Is done over the ./prisma/schema.prisma file with the prisma node_module +- <b>User and Authorization</b>: Is done over entities/UserEntity.ts, prisma/userDBInterface.ts, controller/authController.ts, middleware/auth.ts +- <b>Recipe Selection, Creation, Updating and Deletion</b>: Is done over entities/RecipeEntity.ts, prisma/recipeDBInterface.ts, controller/recipeEntriesController.ts +- <b>Ingerdient Selection, Creation, Updating and Deletion</b>: Is done over entities/IngredientEntity.ts, prisma/ingredientDBInterface.ts, controller/ingredientEntriesController.ts +- <b>CookingSteps Selection, Creation, Updating and Deletion</b>: Is done over entities/CookingStepEntity.ts, prisma/cookingStepDBInterface.ts, controller/cookingStepEntriesController.ts ===========================================================================<br> ## API Routes (die Struktur der Routen auflistet) For now, the server runs on localhost:3000, and depending on the path you can manipulate the data in the database - localhost:3000/auth - - POST localhost:3000/auth/register: Register a new user with following data<br> - {"email" : "test@test.de","password":"test","fName":"Sebastian","lName":"Zill"} - - POST localhost:3000/auth/login: Login a registered user and get the jwt token to authorize access to following routes<br> - {"email": "test@test.de","password": "test"} - + - POST localhost:3000/auth/register: Register a new user with following data + `{"email" : "test@test.de","password":"test","fName":"Sebastian","lName":"Zill"}` + - POST localhost:3000/auth/login: Login a registered user and get the jwt token to authorize access to following routes + `{"email": "test@test.de","password": "test"}` +<br> - localhost:3000/recipeEntries - - POST localhost:3000/recipeEntries: Create new recipe with following input options:<br> - {"rName" : "Nudelauflauf"}<br> - {"rName" : "Nudelauflauf","rDescription" : "Nudeln im Auflauf"}<br> - {"rName" : "Nudelauflauf","rDescription" : "Nudeln im Auflauf","rImg" : "path/to/img.png"} - + - POST localhost:3000/recipeEntries: Create new recipe with following input options: + `{"rName" : "Nudelauflauf"}` + `{"rName" : "Nudelauflauf","rDescription" : "Nudeln im Auflauf"}` + `{"rName" : "Nudelauflauf","rDescription" : "Nudeln im Auflauf","rImg" : "path/to/img.png"}` + <br> - GET localhost:3000/recipeEntries: Query recipes in three modes:<br> - localhost:3000/recipeEntries: Query all recipes<br> - localhost:3000/recipeEntries/1: Query recipe with id 1<br> - localhost:3000/recipeEntries/Nudelauflauf: Query recipe with name Nudelauflauf - - - PUT localhost:3000/recipeEntries/2: Update data for recipe with id 2 with following input options:<br> - {"rName":"RecipeName","rDescription":"Pizza out of the freezer","rImg":"Pizza.png"}<br> - {"rName":"RecipeName","rDescription":"Pizza out of the freezer"}<br> - {"rName":"RecipeName","rImg":"Pizza.png"}<br> - {"rDescription":"Pizza out of the freezer","rImg":"Pizza.png"}<br> - {"rDescription":"Pizza out of the freezer"}<br> - {"rImg":"Pizza.png"} - - - DELETE localhost:3000/recipeEntries/2: Delete recipe with id 2<br> - + - localhost:3000/recipeEntries: Query all recipes<br> + - localhost:3000/recipeEntries/1: Query recipe with id 1<br> + - localhost:3000/recipeEntries/Nudelauflauf: Query recipe with name Nudelauflauf + <br> + - PUT localhost:3000/recipeEntries/2: Update data for recipe with id 2 with following input options: + `{"rName":"RecipeName","rDescription":"Pizza out of the freezer","rImg":"Pizza.png"}` + `{"rName":"RecipeName","rDescription":"Pizza out of the freezer"}` + `{"rName":"RecipeName","rImg":"Pizza.png"}` + `{"rDescription":"Pizza out of the freezer","rImg":"Pizza.png"}` + `{"rDescription":"Pizza out of the freezer"}` + `{"rImg":"Pizza.png"}` + <br> + - DELETE localhost:3000/recipeEntries/: + - localhost:3000/recipeEntries/2: Delete recipe with id 2<br> + <br> - localhost:3000/ingredientEntries - - POST localhost:3000/ingredientEntries: Create new recipe with following input options:<br> - {"iName" : "Nudeln"}<br> - + - POST localhost:3000/ingredientEntries: Create new recipe with following input options: + `{"iName" : "Nudeln"}` + <br> - GET localhost:3000/ingredientEntries: Query ingredients in three modes:<br> - localhost:3000/ingredientEntries: Query all ingredients<br> - localhost:3000/ingredientEntries/1: Query ingredient with id 1<br> - localhost:3000/ingredientEntries/Nudeln: Query ingredient with name Nudeln - - - PUT localhost:3000/ingredientEntries/2: Update data for recipe with id 2 with following input options:<br> - {"iName":"IngredientName"} - - - DELETE localhost:3000/ingredientEntries/2: Delete ingredient with id 2<br> - + - localhost:3000/ingredientEntries: Query all ingredients + - localhost:3000/ingredientEntries/1: Query ingredient with id + - localhost:3000/ingredientEntries/Nudeln: Query ingredient with name Nudeln + <br> + - PUT localhost:3000/ingredientEntries/2: Update data for recipe with id 2 with following input options: + `{"iName":"IngredientName"}` + <br> + - DELETE localhost:3000/ingredientEntries: + - localhost:3000/ingredientEntries/2: Delete ingredient with id 2<br> + <br> - localhost:3000/cookingStepEntries - - POST localhost:3000/cookingStepEntries: Create new cooking steps with following input options (iId and rId have to exist, of course!):<br> - {"description" : "Tomaten hinzugeben", "unit" : "Stück", "amount" : 2, "rId" : 1, "iId" : 2} - + - POST localhost:3000/cookingStepEntries: Create new cooking steps with following input options (iId and rId have to exist, of course!): + `{"description" : "Tomaten hinzugeben", "unit" : "Stück", "amount" : 2, "rId" : 1, "iId" : 2}` + <br> - GET localhost:3000/cookingStepEntries: Query cooking steps with following input options:<br> - null: Query all cooking steps<br> - {"rId" : 0}: Query all cooking steps for recipe with id 0<br> - {"iId" : 0}: Query all cooking steps for ingredient with id 0 - - - PUT localhost:3000/cookingStepEntries/2: Update data for cooking steps with id 2 with following input options:<br> - {"description" : "Tomaten hinzugeben", "unit" : "Stück", "amount" : 2, "rId" : 1, "iId" : 2}<br> - each field is optional! - - - DELETE localhost:3000/cookingStepEntries/2: Delete cooking steps with id 2<br> - + - `null`: Query all cooking steps + - `{"rId" : 0}`: Query all cooking steps for recipe with id 0 + - `{"iId" : 0}`: Query all cooking steps for ingredient with id 0 + <br> + - PUT localhost:3000/cookingStepEntries/2: Update data for cooking steps with id 2 with following input options (each field is optional - combinations as needed!): + `{"description" : "Tomaten hinzugeben", "unit" : "Stück", "amount" : 2, "rId" : 1, "iId" : 2}` + <br> + - DELETE localhost:3000/cookingStepEntries + - localhost:3000/cookingStepEntries/2: Delete cooking steps with id 2 + <br> - localhost:3000/recipeEntriesForIngredient/ingredient: Custom Route "Es soll eine Route geben die alle Rezepte in dem eine bestimmte Zutat existiert zurückgeben kann." - Get all recipes including their cooking steps for the ingredient "ingredient" by its name ## Archive - Long setup I did to get here -cd backend/ -npm init -y -npm i express -npm i ts-node -npm i yup -npm i typescript -D -npm i ts-node -D -npm i tsc-watch -D -npm i @types/express -D -npm i @types/node -D -npm i @types/typescript -D -npm i prisma ts-node -npm i @prisma/client -D +cd backend/<br> +npm init -y<br> +npm i express<br> +npm i ts-node<br> +npm i yup<br> +npm i typescript -D<br> +npm i ts-node -D<br> +npm i tsc-watch -D<br> +npm i @types/express -D<br> +npm i @types/node -D<br> +npm i @types/typescript -D<br> +npm i prisma ts-node<br> +npm i @prisma/client -D<br> npx tsc --init \ No newline at end of file diff --git a/backend/src/controller/cookingStepsController.ts b/backend/src/controller/cookingStepsController.ts index b2dbfbf7c92d83f1aa5c6288f026d942fad0ec7f..1f3b566fd54875464f6a573cc3da0f3b3d62aa71 100644 --- a/backend/src/controller/cookingStepsController.ts +++ b/backend/src/controller/cookingStepsController.ts @@ -88,9 +88,7 @@ router.put("/:id", async (req, res) => { // Check if CookingStep does not exist if ((await selectCookingStepById(Number(id))) != null) { // Check if given recipe and ingredient exist - if ( - await checkIngredientAndRecipeExistence(req.body.rId, req.body.iId) - ) { + if (await checkIngredientAndRecipeExistence(req.body.rId, req.body.iId)) { // Update DB entry updateCookingStep(Number(id), req.body); res.status(202).send(req.body); diff --git a/backend/src/controller/getRecipesForIngredient.ts b/backend/src/controller/getRecipesForIngredient.ts index 157b20cdb46827607455fdfc7ee898bea4d91e03..1080eb0b61313a29c9536ed3853e2b5f124f0faa 100644 --- a/backend/src/controller/getRecipesForIngredient.ts +++ b/backend/src/controller/getRecipesForIngredient.ts @@ -18,7 +18,6 @@ router.get("/:ingredient", async (req, res) => { var iId = null; if (myIngredient != null) iId = myIngredient.iId; - console.log(iId); if (iId != null) { // Get all recipes with this ingredient const myCookingSteps: CookingStepEntity[] = await selectAllCookingStepsForIngredient(iId); diff --git a/backend/src/controller/ingredientEntriesController.ts b/backend/src/controller/ingredientEntriesController.ts index a118a706281d74cb43cb0f9403e7fde8431ecebe..f1e2311bf7937e2e7760f2b2750d9ceef9405b06 100644 --- a/backend/src/controller/ingredientEntriesController.ts +++ b/backend/src/controller/ingredientEntriesController.ts @@ -79,7 +79,6 @@ router.post("/", async (req, res) => { if (validData) { // Check if Ingredient already exists if ((await selectIngredientByName(req.body.iName)) == null) { - // Check if autoincrement id was sent wrongly // Insert into DB createIngredient(req.body); res.status(201).send(req.body); @@ -106,7 +105,7 @@ router.put("/:id", async (req, res) => { res.status(400).send({ errors: e.errors }); }); if (validData) { - // Check if ingredient does not exist + // Check if ingredient exists if ((await selectIngredientById(Number(id))) != null) { if ((await selectIngredientByName(req.body.iName)) == null) { updateIngredient(Number(id), req.body); diff --git a/backend/src/controller/recipeEntriesController.ts b/backend/src/controller/recipeEntriesController.ts index 5b86773d5c79db2f2150a279642928c717118dfa..08318d69e3a585716692c54729c14698280e0151 100644 --- a/backend/src/controller/recipeEntriesController.ts +++ b/backend/src/controller/recipeEntriesController.ts @@ -79,7 +79,6 @@ router.post("/", async (req, res) => { if (validData) { // Check if recipe already exists if ((await selectRecipeByName(req.body.rName)) == null) { - // Check if autoincrement id was sent wrongly // Insert into DB createRecipe(req.body); res.status(201).send(req.body); diff --git a/backend/src/index.ts b/backend/src/index.ts index bd66cad2ae13581eb14496acd7e468b8fcedc857..7f02bad903e88ba011bb4777d2165c7fa3c20fd2 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -31,6 +31,7 @@ export const initializeServer = async () => { // Custom route for "Es soll eine Route geben die alle Rezepte in dem eine bestimmte Zutat existiert zurückgeben kann." app.use("/recipeEntriesForIngredient", Auth.verifyAccess, recipeControllerForIngredient); + DI.server = app.listen(PORT, () => { console.log(`Server started on port ${PORT}`); }); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 7f55f1b77d9eda2241a2c922df558d52bf935c94..0000000000000000000000000000000000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "FWE-SS-23-769544", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}