From d59eb23420b4d5c40f50cd80b0c6c689726d882a Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Thu, 14 Nov 2024 22:52:05 +0100 Subject: [PATCH 01/16] Removed old files --- server/server/.gitignore | 21 --------------------- server/server/Cargo.toml | 8 -------- server/server/src/main.rs | 3 --- 3 files changed, 32 deletions(-) delete mode 100644 server/server/.gitignore delete mode 100644 server/server/Cargo.toml delete mode 100644 server/server/src/main.rs diff --git a/server/server/.gitignore b/server/server/.gitignore deleted file mode 100644 index d01bd1a..0000000 --- a/server/server/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file diff --git a/server/server/Cargo.toml b/server/server/Cargo.toml deleted file mode 100644 index dd022ab..0000000 --- a/server/server/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "server" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/server/server/src/main.rs b/server/server/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/server/server/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} From 928231d2d4b9c2487a86fcafb9cdd7513c865801 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Thu, 14 Nov 2024 23:14:06 +0100 Subject: [PATCH 02/16] Started work on server --- server/src/exception_handlers.py | 16 ++++++++++++++++ server/src/main.py | 12 ++++++++++++ server/src/routes/add_product.py | 2 ++ server/src/routes/routes.py | 4 ++++ 4 files changed, 34 insertions(+) create mode 100644 server/src/exception_handlers.py create mode 100644 server/src/main.py create mode 100644 server/src/routes/add_product.py create mode 100644 server/src/routes/routes.py diff --git a/server/src/exception_handlers.py b/server/src/exception_handlers.py new file mode 100644 index 0000000..77a062a --- /dev/null +++ b/server/src/exception_handlers.py @@ -0,0 +1,16 @@ +import werkzeug +from time import time + +from werkzeug.exceptions import BadRequest, Forbidden, NotFound, MethodNotAllowed, ImATeapot + + +def import_handlers(app): + handlers = [BadRequest, Forbidden, NotFound, MethodNotAllowed, ImATeapot] + + for handler in handlers: + app.register_error_handler(handler, handle_error) + + +def handle_error(e: werkzeug.exceptions.HTTPException): + print(time(), e) + return str(e).split(": ")[0]+"\n", int(e.code) \ No newline at end of file diff --git a/server/src/main.py b/server/src/main.py new file mode 100644 index 0000000..932e2ab --- /dev/null +++ b/server/src/main.py @@ -0,0 +1,12 @@ +from flask import Flask + +from server.src.routes.routes import import_routes +from server.src.exception_handlers import import_handlers + +if __name__ == "__main__": + app = Flask(__name__) + + import_routes(app) + import_handlers(app) + + app.run(debug=True) \ No newline at end of file diff --git a/server/src/routes/add_product.py b/server/src/routes/add_product.py new file mode 100644 index 0000000..dc6f03d --- /dev/null +++ b/server/src/routes/add_product.py @@ -0,0 +1,2 @@ +def add_product(): + return "" \ No newline at end of file diff --git a/server/src/routes/routes.py b/server/src/routes/routes.py new file mode 100644 index 0000000..a849a9d --- /dev/null +++ b/server/src/routes/routes.py @@ -0,0 +1,4 @@ +from server.src.routes.add_product import add_product + +def import_routes(app): + app.post("/add-product")(add_product) \ No newline at end of file From cf828f937614839d2593beb901af469d8f9ab9e8 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 00:30:09 +0100 Subject: [PATCH 03/16] feat: database --- server/src/database/setup.py | 30 ++++++++++++++++++++++ server/src/main.py | 6 +++-- server/src/routes/add_product.py | 2 -- server/src/{ => web}/exception_handlers.py | 0 server/src/web/routes/add_product.py | 17 ++++++++++++ server/src/{ => web}/routes/routes.py | 2 +- 6 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 server/src/database/setup.py delete mode 100644 server/src/routes/add_product.py rename server/src/{ => web}/exception_handlers.py (100%) create mode 100644 server/src/web/routes/add_product.py rename server/src/{ => web}/routes/routes.py (53%) diff --git a/server/src/database/setup.py b/server/src/database/setup.py new file mode 100644 index 0000000..0de90e1 --- /dev/null +++ b/server/src/database/setup.py @@ -0,0 +1,30 @@ +import psycopg2 + +conn = psycopg2.connect( + database="stocky", + host="192.168.1.136", + user="dev", + password="dev", + port="5432" +) + +db = conn.cursor() + +def setup_table(): + try: + db.execute(""" +create table current_stock +( + id bigserial + constraint primary_key + primary key, + name varchar(64) not null, + quantity integer default 0 not null +); + +alter table current_stock + owner to dev; + """) + except psycopg2.errors.DuplicateTable: + print("Table already exists.") + conn.commit() diff --git a/server/src/main.py b/server/src/main.py index 932e2ab..ad88797 100644 --- a/server/src/main.py +++ b/server/src/main.py @@ -1,12 +1,14 @@ from flask import Flask -from server.src.routes.routes import import_routes -from server.src.exception_handlers import import_handlers +from server.src.web.routes.routes import import_routes +from server.src.web.exception_handlers import import_handlers +from server.src.database.setup import setup_table if __name__ == "__main__": app = Flask(__name__) import_routes(app) import_handlers(app) + setup_table() app.run(debug=True) \ No newline at end of file diff --git a/server/src/routes/add_product.py b/server/src/routes/add_product.py deleted file mode 100644 index dc6f03d..0000000 --- a/server/src/routes/add_product.py +++ /dev/null @@ -1,2 +0,0 @@ -def add_product(): - return "" \ No newline at end of file diff --git a/server/src/exception_handlers.py b/server/src/web/exception_handlers.py similarity index 100% rename from server/src/exception_handlers.py rename to server/src/web/exception_handlers.py diff --git a/server/src/web/routes/add_product.py b/server/src/web/routes/add_product.py new file mode 100644 index 0000000..276e53b --- /dev/null +++ b/server/src/web/routes/add_product.py @@ -0,0 +1,17 @@ +from flask import request + +from server.src.database.setup import db, conn + +def add_product(): + + response = request.json + + db.execute(f"SELECT * FROM current_stock WHERE name like '{response["name"]}' ") + + if len(db.fetchall()) != 0: + return '{"status": "400", "msg": "Product with that name already exists"}', 400 + + db.execute(f"INSERT INTO current_stock (name, quantity) VALUES ('{response["name"]}', 0)") + conn.commit() + + return "200 OK" \ No newline at end of file diff --git a/server/src/routes/routes.py b/server/src/web/routes/routes.py similarity index 53% rename from server/src/routes/routes.py rename to server/src/web/routes/routes.py index a849a9d..df0bc05 100644 --- a/server/src/routes/routes.py +++ b/server/src/web/routes/routes.py @@ -1,4 +1,4 @@ -from server.src.routes.add_product import add_product +from server.src.web.routes.add_product import add_product def import_routes(app): app.post("/add-product")(add_product) \ No newline at end of file From f145bbe14dcc1372657541c50c3f68dc6df45917 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 00:37:59 +0100 Subject: [PATCH 04/16] feat: remove_product --- server/src/main.py | 2 +- server/src/web/routes/add_product.py | 3 +-- server/src/web/routes/remove_product.py | 16 ++++++++++++++++ server/src/web/routes/routes.py | 4 ---- server/src/web/routing.py | 6 ++++++ 5 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 server/src/web/routes/remove_product.py delete mode 100644 server/src/web/routes/routes.py create mode 100644 server/src/web/routing.py diff --git a/server/src/main.py b/server/src/main.py index ad88797..4b23c05 100644 --- a/server/src/main.py +++ b/server/src/main.py @@ -1,6 +1,6 @@ from flask import Flask -from server.src.web.routes.routes import import_routes +from server.src.web.routing import import_routes from server.src.web.exception_handlers import import_handlers from server.src.database.setup import setup_table diff --git a/server/src/web/routes/add_product.py b/server/src/web/routes/add_product.py index 276e53b..039e155 100644 --- a/server/src/web/routes/add_product.py +++ b/server/src/web/routes/add_product.py @@ -3,13 +3,12 @@ from server.src.database.setup import db, conn def add_product(): - response = request.json db.execute(f"SELECT * FROM current_stock WHERE name like '{response["name"]}' ") if len(db.fetchall()) != 0: - return '{"status": "400", "msg": "Product with that name already exists"}', 400 + return '{"status": "400", "msg": "Product with that name already exists."}', 400 db.execute(f"INSERT INTO current_stock (name, quantity) VALUES ('{response["name"]}', 0)") conn.commit() diff --git a/server/src/web/routes/remove_product.py b/server/src/web/routes/remove_product.py new file mode 100644 index 0000000..04a98dd --- /dev/null +++ b/server/src/web/routes/remove_product.py @@ -0,0 +1,16 @@ +from flask import request + +from server.src.database.setup import db, conn + +def remove_product(): + response = request.json + + db.execute(f"SELECT * FROM current_stock WHERE name like '{response["name"]}' ") + + if len(db.fetchall()) == 0: + return '{"status": "400", "msg": "Product with that name does not exist."}', 400 + + db.execute(f"DELETE FROM current_stock WHERE name like '{response["name"]}' ") + conn.commit() + + return "200 OK" \ No newline at end of file diff --git a/server/src/web/routes/routes.py b/server/src/web/routes/routes.py deleted file mode 100644 index df0bc05..0000000 --- a/server/src/web/routes/routes.py +++ /dev/null @@ -1,4 +0,0 @@ -from server.src.web.routes.add_product import add_product - -def import_routes(app): - app.post("/add-product")(add_product) \ No newline at end of file diff --git a/server/src/web/routing.py b/server/src/web/routing.py new file mode 100644 index 0000000..8497db4 --- /dev/null +++ b/server/src/web/routing.py @@ -0,0 +1,6 @@ +from server.src.web.routes.add_product import add_product +from server.src.web.routes.remove_product import remove_product + +def import_routes(app): + app.post("/add-product")(add_product) + app.post("/remove-product")(remove_product) \ No newline at end of file From bf41a3c5527a3239fdf5670449161054755d922a Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 00:43:56 +0100 Subject: [PATCH 05/16] Comment --- server/src/database/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/database/setup.py b/server/src/database/setup.py index 0de90e1..a78dcc7 100644 --- a/server/src/database/setup.py +++ b/server/src/database/setup.py @@ -1,5 +1,6 @@ import psycopg2 +# TODO Add these into a .env file, this is suboptimally secure :) conn = psycopg2.connect( database="stocky", host="192.168.1.136", From 8f3e05ad9e4968998748c1dd9370664ed1912c93 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 00:58:02 +0100 Subject: [PATCH 06/16] .gitignore update --- .gitignore | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index d01bd1a..46f3385 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +.idea +**/__pycache__/ \ No newline at end of file From f0a9877d810a026f476c4e9e9cef6491a8e6e00c Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 16:38:42 +0100 Subject: [PATCH 07/16] feat: /transaction --- server/src/web/routes/transaction.py | 38 ++++++++++++++++++++++++++++ server/src/web/utils.py | 10 ++++++++ 2 files changed, 48 insertions(+) create mode 100644 server/src/web/routes/transaction.py create mode 100644 server/src/web/utils.py diff --git a/server/src/web/routes/transaction.py b/server/src/web/routes/transaction.py new file mode 100644 index 0000000..70da8f7 --- /dev/null +++ b/server/src/web/routes/transaction.py @@ -0,0 +1,38 @@ +from flask import request +from werkzeug.exceptions import BadRequest + +from server.src.database.setup import db, conn +from server.src.web.utils import filter_name + + +# { +# "items": [ +# { +# "name": "waffles", +# "quantity": 21 +# } +# ] +# } +def transaction(): + data = request.json + + # Check if all items exist in the database and if there's enough in stock + for product in data["items"]: + # Enforce that the quantity is a positive integer + if not isinstance(product["quantity"], int) or product["quantity"] < 0: + raise BadRequest + + filtered_name = filter_name(product["name"]) + + db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}'") + query = db.fetchall() + if len(query) == 0 or query[0][2] < product["quantity"]: + raise BadRequest + + # Deduct stock + for product in data["items"]: + filtered_name = product["name"].split(" ")[0] + db.execute(f"UPDATE public.current_stock SET quantity = quantity - {product["quantity"]} WHERE name LIKE '{filtered_name}';") + conn.commit() + + return "200 OK\n" \ No newline at end of file diff --git a/server/src/web/utils.py b/server/src/web/utils.py new file mode 100644 index 0000000..0a34a88 --- /dev/null +++ b/server/src/web/utils.py @@ -0,0 +1,10 @@ +from werkzeug.exceptions import BadRequest + + +def filter_name(name): + # Enforce that the quantity is a string + if not isinstance(name, str): + raise BadRequest + + # To prevent injections, only accept product names with no spaces + return name.split(" ")[0] \ No newline at end of file From 37da6785fc6196c9a9ce355d501047be1cb91ff8 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 16:39:28 +0100 Subject: [PATCH 08/16] Did some work to prevent SQL injections and input filtering --- server/src/web/exception_handlers.py | 1 - server/src/web/routes/add_product.py | 12 ++++++++---- server/src/web/routes/remove_product.py | 13 +++++++++---- server/src/web/routing.py | 5 ++++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/server/src/web/exception_handlers.py b/server/src/web/exception_handlers.py index 77a062a..57075b0 100644 --- a/server/src/web/exception_handlers.py +++ b/server/src/web/exception_handlers.py @@ -12,5 +12,4 @@ def import_handlers(app): def handle_error(e: werkzeug.exceptions.HTTPException): - print(time(), e) return str(e).split(": ")[0]+"\n", int(e.code) \ No newline at end of file diff --git a/server/src/web/routes/add_product.py b/server/src/web/routes/add_product.py index 039e155..4875046 100644 --- a/server/src/web/routes/add_product.py +++ b/server/src/web/routes/add_product.py @@ -1,16 +1,20 @@ from flask import request +from werkzeug.exceptions import BadRequest from server.src.database.setup import db, conn +from server.src.web.utils import filter_name + def add_product(): - response = request.json + data = request.json + filtered_name = filter_name(data["name"]) - db.execute(f"SELECT * FROM current_stock WHERE name like '{response["name"]}' ") + db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") if len(db.fetchall()) != 0: - return '{"status": "400", "msg": "Product with that name already exists."}', 400 + raise BadRequest - db.execute(f"INSERT INTO current_stock (name, quantity) VALUES ('{response["name"]}', 0)") + db.execute(f"INSERT INTO current_stock (name, quantity) VALUES ('{filtered_name}', 0)") conn.commit() return "200 OK" \ No newline at end of file diff --git a/server/src/web/routes/remove_product.py b/server/src/web/routes/remove_product.py index 04a98dd..63f837d 100644 --- a/server/src/web/routes/remove_product.py +++ b/server/src/web/routes/remove_product.py @@ -1,16 +1,21 @@ from flask import request +from werkzeug.exceptions import BadRequest from server.src.database.setup import db, conn +from server.src.web.utils import filter_name + def remove_product(): - response = request.json + data = request.json + # To prevent injections, only accept product names with no spaces + filtered_name = filter_name(data["name"]) - db.execute(f"SELECT * FROM current_stock WHERE name like '{response["name"]}' ") + db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") if len(db.fetchall()) == 0: - return '{"status": "400", "msg": "Product with that name does not exist."}', 400 + raise BadRequest - db.execute(f"DELETE FROM current_stock WHERE name like '{response["name"]}' ") + db.execute(f"DELETE FROM current_stock WHERE name LIKE '{filtered_name}' ") conn.commit() return "200 OK" \ No newline at end of file diff --git a/server/src/web/routing.py b/server/src/web/routing.py index 8497db4..e264a25 100644 --- a/server/src/web/routing.py +++ b/server/src/web/routing.py @@ -1,6 +1,9 @@ from server.src.web.routes.add_product import add_product from server.src.web.routes.remove_product import remove_product +from server.src.web.routes.transaction import transaction + def import_routes(app): app.post("/add-product")(add_product) - app.post("/remove-product")(remove_product) \ No newline at end of file + app.post("/remove-product")(remove_product) + app.post("/transaction")(transaction) \ No newline at end of file From dc5075bbcc6d2471ca502e1e5ab550f19ce8a5fa Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 16:43:35 +0100 Subject: [PATCH 09/16] Added "documentation" for JSON structure of endpoints --- server/src/web/routes/add_product.py | 4 +++- server/src/web/routes/remove_product.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/web/routes/add_product.py b/server/src/web/routes/add_product.py index 4875046..e2319f8 100644 --- a/server/src/web/routes/add_product.py +++ b/server/src/web/routes/add_product.py @@ -4,7 +4,9 @@ from server.src.database.setup import db, conn from server.src.web.utils import filter_name - +# { +# "name": "waffles", +# } def add_product(): data = request.json filtered_name = filter_name(data["name"]) diff --git a/server/src/web/routes/remove_product.py b/server/src/web/routes/remove_product.py index 63f837d..7ac2f7a 100644 --- a/server/src/web/routes/remove_product.py +++ b/server/src/web/routes/remove_product.py @@ -4,7 +4,9 @@ from server.src.database.setup import db, conn from server.src.web.utils import filter_name - +# { +# "name": "waffles", +# } def remove_product(): data = request.json # To prevent injections, only accept product names with no spaces From d4141f0b7d655e13272a1e03d7df69d9d804823d Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 17:41:18 +0100 Subject: [PATCH 10/16] change: streamlined raising errors --- server/src/main.py | 2 +- server/src/web/exception_handlers.py | 3 +-- .../routes/{add_product.py => product/add.py} | 7 ++++--- .../{remove_product.py => product/remove.py} | 7 ++++--- server/src/web/routes/transaction.py | 12 +++++++----- server/src/web/routing.py | 16 ++++++++++------ server/src/web/utils.py | 4 ++-- 7 files changed, 29 insertions(+), 22 deletions(-) rename server/src/web/routes/{add_product.py => product/add.py} (81%) rename server/src/web/routes/{remove_product.py => product/remove.py} (82%) diff --git a/server/src/main.py b/server/src/main.py index 4b23c05..0bea1d2 100644 --- a/server/src/main.py +++ b/server/src/main.py @@ -11,4 +11,4 @@ import_handlers(app) setup_table() - app.run(debug=True) \ No newline at end of file + app.run(debug=True) diff --git a/server/src/web/exception_handlers.py b/server/src/web/exception_handlers.py index 57075b0..bdcbc1a 100644 --- a/server/src/web/exception_handlers.py +++ b/server/src/web/exception_handlers.py @@ -1,5 +1,4 @@ import werkzeug -from time import time from werkzeug.exceptions import BadRequest, Forbidden, NotFound, MethodNotAllowed, ImATeapot @@ -12,4 +11,4 @@ def import_handlers(app): def handle_error(e: werkzeug.exceptions.HTTPException): - return str(e).split(": ")[0]+"\n", int(e.code) \ No newline at end of file + return f"{e.code} {str(e.description)}\n", e.code diff --git a/server/src/web/routes/add_product.py b/server/src/web/routes/product/add.py similarity index 81% rename from server/src/web/routes/add_product.py rename to server/src/web/routes/product/add.py index e2319f8..e3e3e7d 100644 --- a/server/src/web/routes/add_product.py +++ b/server/src/web/routes/product/add.py @@ -4,19 +4,20 @@ from server.src.database.setup import db, conn from server.src.web.utils import filter_name + # { # "name": "waffles", # } -def add_product(): +def add(): data = request.json filtered_name = filter_name(data["name"]) db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") if len(db.fetchall()) != 0: - raise BadRequest + raise BadRequest(description=f"Product {filtered_name} already exists") db.execute(f"INSERT INTO current_stock (name, quantity) VALUES ('{filtered_name}', 0)") conn.commit() - return "200 OK" \ No newline at end of file + return "200 OK\n" diff --git a/server/src/web/routes/remove_product.py b/server/src/web/routes/product/remove.py similarity index 82% rename from server/src/web/routes/remove_product.py rename to server/src/web/routes/product/remove.py index 7ac2f7a..a580256 100644 --- a/server/src/web/routes/remove_product.py +++ b/server/src/web/routes/product/remove.py @@ -4,10 +4,11 @@ from server.src.database.setup import db, conn from server.src.web.utils import filter_name + # { # "name": "waffles", # } -def remove_product(): +def remove(): data = request.json # To prevent injections, only accept product names with no spaces filtered_name = filter_name(data["name"]) @@ -15,9 +16,9 @@ def remove_product(): db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") if len(db.fetchall()) == 0: - raise BadRequest + raise BadRequest(description=f"Product {filtered_name} doesn't exist") db.execute(f"DELETE FROM current_stock WHERE name LIKE '{filtered_name}' ") conn.commit() - return "200 OK" \ No newline at end of file + return "200 OK\n" diff --git a/server/src/web/routes/transaction.py b/server/src/web/routes/transaction.py index 70da8f7..9dad45b 100644 --- a/server/src/web/routes/transaction.py +++ b/server/src/web/routes/transaction.py @@ -20,19 +20,21 @@ def transaction(): for product in data["items"]: # Enforce that the quantity is a positive integer if not isinstance(product["quantity"], int) or product["quantity"] < 0: - raise BadRequest + raise BadRequest(description="Invalid quantity") filtered_name = filter_name(product["name"]) db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}'") query = db.fetchall() - if len(query) == 0 or query[0][2] < product["quantity"]: - raise BadRequest + if len(query) == 0: + raise BadRequest(description=f"Product {filtered_name} not found") + if query[0][2] < product["quantity"]: + raise BadRequest(description=f"Not enough stock for {filtered_name}") # Deduct stock for product in data["items"]: filtered_name = product["name"].split(" ")[0] - db.execute(f"UPDATE public.current_stock SET quantity = quantity - {product["quantity"]} WHERE name LIKE '{filtered_name}';") + db.execute(f"UPDATE current_stock SET quantity = quantity - {product["quantity"]} WHERE name LIKE '{filtered_name}';") conn.commit() - return "200 OK\n" \ No newline at end of file + return "200 OK\n" diff --git a/server/src/web/routing.py b/server/src/web/routing.py index e264a25..6afaa2e 100644 --- a/server/src/web/routing.py +++ b/server/src/web/routing.py @@ -1,9 +1,13 @@ -from server.src.web.routes.add_product import add_product -from server.src.web.routes.remove_product import remove_product -from server.src.web.routes.transaction import transaction +from server.src.web.routes.product.add import * +from server.src.web.routes.product.remove import * +from server.src.web.routes.product.status import * +from server.src.web.routes.transaction import * def import_routes(app): - app.post("/add-product")(add_product) - app.post("/remove-product")(remove_product) - app.post("/transaction")(transaction) \ No newline at end of file + app.post("/product/add")(add) + app.delete("/product/remove")(remove) + app.post("/transaction")(transaction) + + app.get("/product/status")(status_get) + app.post("/product/status")(status_post) \ No newline at end of file diff --git a/server/src/web/utils.py b/server/src/web/utils.py index 0a34a88..de96bd0 100644 --- a/server/src/web/utils.py +++ b/server/src/web/utils.py @@ -4,7 +4,7 @@ def filter_name(name): # Enforce that the quantity is a string if not isinstance(name, str): - raise BadRequest + raise BadRequest(description=f"Invalid product name") # To prevent injections, only accept product names with no spaces - return name.split(" ")[0] \ No newline at end of file + return name.split(" ")[0] From 7fbe863729a1b4b3e6195900c410af9f82deaf96 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Fri, 15 Nov 2024 17:41:31 +0100 Subject: [PATCH 11/16] feat: /product/status --- server/src/web/routes/product/status.py | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 server/src/web/routes/product/status.py diff --git a/server/src/web/routes/product/status.py b/server/src/web/routes/product/status.py new file mode 100644 index 0000000..0d26686 --- /dev/null +++ b/server/src/web/routes/product/status.py @@ -0,0 +1,33 @@ +from flask import request +from werkzeug.exceptions import BadRequest + +from server.src.database.setup import db, conn +from server.src.web.utils import filter_name + + +def status_get(): + data = request.json + filtered_name = filter_name(data["name"]) + + db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") + + query = db.fetchall() + + if len(query) == 0: + raise BadRequest(description=f"Product {filtered_name} doesn't exist") + + return {"quantity": query[0][2]}, 200 + + +def status_post(): + data = request.json + filtered_name = filter_name(data["name"]) + + # Enforce that the quantity is a positive integer + if not isinstance(data["quantity"], int) or data["quantity"] < 0: + raise BadRequest(description="Invalid quantity") + + db.execute(f"UPDATE current_stock SET quantity = {data["quantity"]} WHERE name LIKE '{filtered_name}';") + conn.commit() + + return "200 OK\n" \ No newline at end of file From c13f7123f1d5a444104f33acf85d22295d9fc7aa Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Mon, 18 Nov 2024 17:39:55 +0100 Subject: [PATCH 12/16] Added JSON structure checks --- server/src/web/routes/product/add.py | 5 +++++ server/src/web/routes/product/remove.py | 4 ++++ server/src/web/routes/product/status.py | 4 ++++ server/src/web/routes/transaction.py | 9 ++++++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/server/src/web/routes/product/add.py b/server/src/web/routes/product/add.py index e3e3e7d..f25e0df 100644 --- a/server/src/web/routes/product/add.py +++ b/server/src/web/routes/product/add.py @@ -10,6 +10,11 @@ # } def add(): data = request.json + + if "name" not in data: + raise BadRequest("Bad JSON structure") + + # To prevent injections, only accept product names with no spaces filtered_name = filter_name(data["name"]) db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") diff --git a/server/src/web/routes/product/remove.py b/server/src/web/routes/product/remove.py index a580256..91a565a 100644 --- a/server/src/web/routes/product/remove.py +++ b/server/src/web/routes/product/remove.py @@ -10,6 +10,10 @@ # } def remove(): data = request.json + + if "name" not in data: + raise BadRequest("Bad JSON structure") + # To prevent injections, only accept product names with no spaces filtered_name = filter_name(data["name"]) diff --git a/server/src/web/routes/product/status.py b/server/src/web/routes/product/status.py index 0d26686..162730f 100644 --- a/server/src/web/routes/product/status.py +++ b/server/src/web/routes/product/status.py @@ -7,6 +7,10 @@ def status_get(): data = request.json + + if "name" not in data: + raise BadRequest("Bad JSON structure") + filtered_name = filter_name(data["name"]) db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") diff --git a/server/src/web/routes/transaction.py b/server/src/web/routes/transaction.py index 9dad45b..67441ca 100644 --- a/server/src/web/routes/transaction.py +++ b/server/src/web/routes/transaction.py @@ -14,10 +14,17 @@ # ] # } def transaction(): - data = request.json + data: dict = request.json + + if "items" not in data: + raise BadRequest("Bad JSON structure") # Check if all items exist in the database and if there's enough in stock for product in data["items"]: + if "name" not in data or "quantity" not in data: + raise BadRequest("Bad JSON structure") + + # Enforce that the quantity is a positive integer if not isinstance(product["quantity"], int) or product["quantity"] < 0: raise BadRequest(description="Invalid quantity") From 6bb3e17ff4e6debbbfa98f01f69b6c90fcc39909 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Mon, 18 Nov 2024 17:40:12 +0100 Subject: [PATCH 13/16] new: requirements.txt --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..35f30cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask==3.0.3 +werkzeug==3.1.3 +psycopg2-binary==2.9.10 \ No newline at end of file From 52fddc9d177ef949066c11c536c383f0f7723d86 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Mon, 18 Nov 2024 17:49:30 +0100 Subject: [PATCH 14/16] fix: transaction checks from data json instead of from product json --- server/src/web/routes/transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/web/routes/transaction.py b/server/src/web/routes/transaction.py index 67441ca..12b5ced 100644 --- a/server/src/web/routes/transaction.py +++ b/server/src/web/routes/transaction.py @@ -21,7 +21,7 @@ def transaction(): # Check if all items exist in the database and if there's enough in stock for product in data["items"]: - if "name" not in data or "quantity" not in data: + if "name" not in product or "quantity" not in product: raise BadRequest("Bad JSON structure") From 7b807a705a2e9c4cf52d78d17b98d19fe32bc063 Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Mon, 18 Nov 2024 17:50:14 +0100 Subject: [PATCH 15/16] Removed unnecessary 'description=' BadRequest constructors --- server/src/web/routes/product/add.py | 2 +- server/src/web/routes/product/remove.py | 2 +- server/src/web/routes/product/status.py | 4 ++-- server/src/web/routes/transaction.py | 6 +++--- server/src/web/utils.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/web/routes/product/add.py b/server/src/web/routes/product/add.py index f25e0df..ada4119 100644 --- a/server/src/web/routes/product/add.py +++ b/server/src/web/routes/product/add.py @@ -20,7 +20,7 @@ def add(): db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") if len(db.fetchall()) != 0: - raise BadRequest(description=f"Product {filtered_name} already exists") + raise BadRequest(f"Product {filtered_name} already exists") db.execute(f"INSERT INTO current_stock (name, quantity) VALUES ('{filtered_name}', 0)") conn.commit() diff --git a/server/src/web/routes/product/remove.py b/server/src/web/routes/product/remove.py index 91a565a..347dee3 100644 --- a/server/src/web/routes/product/remove.py +++ b/server/src/web/routes/product/remove.py @@ -20,7 +20,7 @@ def remove(): db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}' ") if len(db.fetchall()) == 0: - raise BadRequest(description=f"Product {filtered_name} doesn't exist") + raise BadRequest(f"Product {filtered_name} doesn't exist") db.execute(f"DELETE FROM current_stock WHERE name LIKE '{filtered_name}' ") conn.commit() diff --git a/server/src/web/routes/product/status.py b/server/src/web/routes/product/status.py index 162730f..ba58380 100644 --- a/server/src/web/routes/product/status.py +++ b/server/src/web/routes/product/status.py @@ -18,7 +18,7 @@ def status_get(): query = db.fetchall() if len(query) == 0: - raise BadRequest(description=f"Product {filtered_name} doesn't exist") + raise BadRequest(f"Product {filtered_name} doesn't exist") return {"quantity": query[0][2]}, 200 @@ -29,7 +29,7 @@ def status_post(): # Enforce that the quantity is a positive integer if not isinstance(data["quantity"], int) or data["quantity"] < 0: - raise BadRequest(description="Invalid quantity") + raise BadRequest("Invalid quantity") db.execute(f"UPDATE current_stock SET quantity = {data["quantity"]} WHERE name LIKE '{filtered_name}';") conn.commit() diff --git a/server/src/web/routes/transaction.py b/server/src/web/routes/transaction.py index 12b5ced..6e4d624 100644 --- a/server/src/web/routes/transaction.py +++ b/server/src/web/routes/transaction.py @@ -27,16 +27,16 @@ def transaction(): # Enforce that the quantity is a positive integer if not isinstance(product["quantity"], int) or product["quantity"] < 0: - raise BadRequest(description="Invalid quantity") + raise BadRequest("Invalid quantity") filtered_name = filter_name(product["name"]) db.execute(f"SELECT * FROM current_stock WHERE name LIKE '{filtered_name}'") query = db.fetchall() if len(query) == 0: - raise BadRequest(description=f"Product {filtered_name} not found") + raise BadRequest(f"Product {filtered_name} not found") if query[0][2] < product["quantity"]: - raise BadRequest(description=f"Not enough stock for {filtered_name}") + raise BadRequest(f"Not enough stock for {filtered_name}") # Deduct stock for product in data["items"]: diff --git a/server/src/web/utils.py b/server/src/web/utils.py index de96bd0..e5569d9 100644 --- a/server/src/web/utils.py +++ b/server/src/web/utils.py @@ -4,7 +4,7 @@ def filter_name(name): # Enforce that the quantity is a string if not isinstance(name, str): - raise BadRequest(description=f"Invalid product name") + raise BadRequest(f"Invalid product name") # To prevent injections, only accept product names with no spaces return name.split(" ")[0] From da8e536f0a6cfd0ed6a3c576b84f190868d5cc1a Mon Sep 17 00:00:00 2001 From: sam32123 Date: Mon, 25 Nov 2024 17:41:10 +0100 Subject: [PATCH 16/16] updated CREATE TABLE to comply with satex idea. Webapp should be modified to support this --- server/src/database/setup.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/server/src/database/setup.py b/server/src/database/setup.py index a78dcc7..f072a9b 100644 --- a/server/src/database/setup.py +++ b/server/src/database/setup.py @@ -14,17 +14,33 @@ def setup_table(): try: db.execute(""" -create table current_stock -( - id bigserial - constraint primary_key - primary key, - name varchar(64) not null, - quantity integer default 0 not null +CREATE TABLE Producten ( + Product_ID SERIAL PRIMARY KEY, + Product_Naam VARCHAR(255) NOT NULL, + Product_Aantal INT NOT NULL, + Product_Barcode VARCHAR(255) ); -alter table current_stock - owner to dev; +CREATE TABLE Transacties ( + Transactie_ID SERIAL PRIMARY KEY, + Product_ID INT NOT NULL, + Date_Time TIMESTAMP NOT NULL, + Transactie_Aantal INT NOT NULL, + FOREIGN KEY (Product_ID) REFERENCES Producten(Product_ID) +); + +CREATE TABLE Derving ( + Transactie_ID INT NOT NULL, + User_ID INT NOT NULL, + Derving_Type VARCHAR(255), + PRIMARY KEY (Transactie_ID, User_ID), + FOREIGN KEY (Transactie_ID) REFERENCES Transacties(Transactie_ID) +); + +CREATE TABLE Succesvolle_Transacties ( + Transactie_ID INT PRIMARY KEY, + FOREIGN KEY (Transactie_ID) REFERENCES Transacties(Transactie_ID) +); """) except psycopg2.errors.DuplicateTable: print("Table already exists.")