From 684ed3f130a978d0defb70b51b0d583bf41724ef Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 10:51:45 +0200 Subject: [PATCH 01/38] Ifnore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index cdaa90a..151a90e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ krusty.sqlite3 *.db *.tar.gz *.minisig +*.jpg +*.pdf From 8dc532c6cc3c90668809f48320900dc26f0b9687 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 10:52:12 +0200 Subject: [PATCH 02/38] Makefile target for generating ERD diagram with eralchemy --- makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/makefile b/makefile index dc22d2c..d74125e 100644 --- a/makefile +++ b/makefile @@ -25,4 +25,8 @@ release: scp krusty-imbus_$(GITHASH).tar.gz server:/public/krusty/krusty-imbus_$(GITHASH).tar.gz scp krusty-imbus_$(GITHASH).tar.gz.minisig server:/public/krusty/krusty-imbus_$(GITHASH).tar.gz.minisig -.PHONY: run clean test build dbdump migrate release +# Generate ERD. Requires eralchemy2 (pip install eralchemy2) +erd: + eralchemy2 -i sqlite:///app/krusty.db -o erd.jpg + +.PHONY: run clean test build dbdump migrate release erd From 29b68d05e0b32f8ab94664afa9bbf4f84be8f8e7 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 12:54:55 +0200 Subject: [PATCH 03/38] Readme, initial --- README.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a449f82 --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +# Krusty Cookies + +> Krusty Kookies is a bakery which specializes in cookies, and they need a database to keep track of their production and deliveries. + +### Base tables + +Unsuprisingly, we will need a cookie table. + +```sql +CREATE TABLE cookie ( + cookie_id INT PRIMARY KEY, + cookie_name VARCHAR(50) NOT NULL, +); +``` + +Last i checked, a commercial bakery needs customers: + +```sql +CREATE TABLE customer ( + customer_id INT PRIMARY KEY, + customer_name VARCHAR(50) NOT NULL, + customer_address VARCHAR(50) NOT NULL, +); +``` + +We could also have a recipe table that relates ingredients to cookies. But instead, we just keep track of inventory (raw materials) and let the business logic handle orders/production by subtracting a certain set of ingredients from the inventory, and adding the corresponding pallets to the "freezer". + +```sql +CREATE TABLE raw_materials ( + ingredient_id INT PRIMARY KEY, + ingredient_name VARCHAR(50) NOT NULL UNIQUE, + ingredient_quantity INT NOT NULL, + unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')) +); +``` + +> When a pallet is produced, the raw materials storage must be updated, and the company must be able to check the amount in store of each ingredient, and to see when, and **how much of**, an ingredient was last delivered into storage. + +Because of the 'how much of' part, we cannot simply record a last_delivery field in the raw_materials table. + +We will use a separate table to keep track of increments in inventory (raw material deliveries). + +```sql +CREATE TABLE raw_materials_deliveries ( + delivery_id INT PRIMARY KEY, + ingredient_id INT NOT NULL, + delivery_date DATE NOT NULL, + delivery_quantity INT NOT NULL, + unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')), + FOREIGN KEY (ingredient_id) REFERENCES raw_materials(ingredient_id) +); +``` + +When recieving new inventory, we need to initiate a transaction that updates the inventory table, and adds a new row to the raw_mat_delivery table. + +### Pallets and orders + +> The cookies are baked in large quantities, and then quickly frozen and packaged in bags with 15 cookies in each bag. The bags are put into boxes, with 10 bags per box. Finally, the boxes are stacked on pallets, where each pallet contains 36 boxes. + +15 x 10 x 36 = 5400 cookies per pallet. So, to produce a pallet: calculate the total material cost and subtract it from the inventory. + +> The company only delivers to to wholesale Links to an external site. customers, and a typical order looks like “send 10 pallets of Tango cookies, and 6 pallets of Berliners to Kalaskakor AB” – pallets are the unit of all orders (i.e., you can’t break up a pallet in an order). + +This is reiterating the fact that 'pallet' is the atomic unit of orders. Furthermore: + +> A pallet is considered to be produced when the pallet label is read at the entrance to the deep-freeze storage. The pallet number, product name, and date and time of production are registered in the database. The pallet number is unique. + +Conceptually, what happens before the cookies arrive in the freezer is not interesting to us. We only care about the final pallets. The number of cookies per pallet is also of intrest, to keep track of the inventory (recipes and subsequent raw_materials subtractions are handled by the business logic). + +Either we have pallets in the freezer, or we make more pallets. If we do not have enough inventory to make a pallet, we can't make a pallet, and the order is rejected. + +```sql +CREATE TABLE pallets ( + pallet_id INT PRIMARY KEY, + cookie_id INT NOT NULL, + status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), + production_date DATE NOT NULL, + FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) +); +``` + +> When the truck is fully loaded, the driver receives a loading bill containing customer names, addresses, and the number of pallets of each product that is to be delivered to each customer. A transport may contain deliveries intended for different customers. + +This suggests that we may need to relate pallets to customers via a truck entity, however, this truck entity is never referenced, so we can omit it entirely. + +> On delivery, pallets are transported from the deep-freeze storeroom via a loading ramp to the freezer trucks – each truck loads 60 pallets. The entry to the loading ramp contains a bar code reader which reads the pallet label. Pallets must be loaded in production date order. + +Again, the truck as an entity is irrelevant and we only need a table to keep track of what pallet was delivered to what customer along with a date. + +```sql +CREATE TABLE deliveries ( + delivery_id INT PRIMARY KEY, + pallet_id INT NOT NULL, + customer_name VARCHAR(50) NOT NULL, + delivery_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (delivery_date >= CURRENT_DATE), + FOREIGN KEY (pallet_id) REFERENCES pallets(pallet_id) +); +``` + +> The company continuously take random samples among the products, and the samples are analyzed in their laboratory. If a sample doesn’t meet their quality standards, all pallets containing that product which have been produced during a specific time interval are blocked. A blocked pallet may not be delivered to customers. + +So, our pallet table needs a status column. This conveniently fits as an enum of 'freezer', 'delivered' and 'blocked'. + +> All pallets must be traceable, for instance, the company needs to be able to see all information about a pallet with a given number (the contents of the pallet, the location of the pallet, if the pallet is delivered and in that case to whom, etc.). They must also be able to see which pallets contain a certain product and which pallets have been produced during a certain time interval. + +This should all be possible with a simple join query. + +> Blocked products are of special interest. The company needs to find out **which products are blocked**, and also which pallets contain a certain blocked product. + +> Finally, they must be able to check **which pallets have been delivered** to a given customer, and the date and time of delivery. + +> Orders must be registered in the database, and, for production planning purposes, the company must be able to see all orders which are to be **delivered during a specific time period**. + +These are all trivial queries. From 0445d0f7953ea56ab83ca1b36567ff7a229a121f Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:21:44 +0200 Subject: [PATCH 04/38] Order table --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index a449f82..aa1250c 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,21 @@ CREATE TABLE deliveries ( ); ``` +> Orders must be registered in the database, and, for production planning purposes, the company must be able to see all orders which are to be delivered during a specific time period. + +Note that the individial pallets hold the delivery date. + +```sql +CREATE TABLE orders ( + order_id INT PRIMARY KEY, + customer_id INT NOT NULL, + cookie_id INT NOT NULL, + order_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (order_date >= CURRENT_DATE), + FOREIGN KEY (customer_id) REFERENCES customer(customer_id), + FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) +); +``` + > The company continuously take random samples among the products, and the samples are analyzed in their laboratory. If a sample doesn’t meet their quality standards, all pallets containing that product which have been produced during a specific time interval are blocked. A blocked pallet may not be delivered to customers. So, our pallet table needs a status column. This conveniently fits as an enum of 'freezer', 'delivered' and 'blocked'. From 5513fd445e6e592d6c3eb28ea50efa3f4bb5e246 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:28:40 +0200 Subject: [PATCH 05/38] Readme changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa1250c..d3eb2e1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Unsuprisingly, we will need a cookie table. ```sql CREATE TABLE cookie ( cookie_id INT PRIMARY KEY, - cookie_name VARCHAR(50) NOT NULL, + cookie_name VARCHAR(50) NOT NULL UNIQUE, ); ``` From d9dc63ae256202cba73cee15fcecfe4028aea70e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:29:12 +0200 Subject: [PATCH 06/38] Readme changes again --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d3eb2e1..05520aa 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,11 @@ Either we have pallets in the freezer, or we make more pallets. If we do not hav CREATE TABLE pallets ( pallet_id INT PRIMARY KEY, cookie_id INT NOT NULL, + order_id INT NOT NULL, status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), production_date DATE NOT NULL, FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) + FOREIGN KEY (order_id) REFERENCES orders(order_id) ); ``` From bd4257e790d66cb3f3865b2047be4743d8f5201d Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:32:53 +0200 Subject: [PATCH 07/38] markdown syntax errors --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 05520aa..375bc08 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Krusty Kookies is a bakery which specializes in cookies, and they need a database to keep track of their production and deliveries. -### Base tables +## Base tables Unsuprisingly, we will need a cookie table. @@ -90,13 +90,6 @@ This suggests that we may need to relate pallets to customers via a truck entity Again, the truck as an entity is irrelevant and we only need a table to keep track of what pallet was delivered to what customer along with a date. ```sql -CREATE TABLE deliveries ( - delivery_id INT PRIMARY KEY, - pallet_id INT NOT NULL, - customer_name VARCHAR(50) NOT NULL, - delivery_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (delivery_date >= CURRENT_DATE), - FOREIGN KEY (pallet_id) REFERENCES pallets(pallet_id) -); ``` > Orders must be registered in the database, and, for production planning purposes, the company must be able to see all orders which are to be delivered during a specific time period. @@ -124,8 +117,12 @@ This should all be possible with a simple join query. > Blocked products are of special interest. The company needs to find out **which products are blocked**, and also which pallets contain a certain blocked product. +As well as: + > Finally, they must be able to check **which pallets have been delivered** to a given customer, and the date and time of delivery. +And: + > Orders must be registered in the database, and, for production planning purposes, the company must be able to see all orders which are to be **delivered during a specific time period**. These are all trivial queries. From dd1781fa71ee746247fd48d48535dd264dc01ec3 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:33:24 +0200 Subject: [PATCH 08/38] make db consistent with readme --- app/Migrations/create-schema.sql | 87 +++++++++++++------------------- 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 4078831..20663a8 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -4,50 +4,41 @@ -- Our known customers, may need more fields CREATE TABLE IF NOT EXISTS customers ( - customer_id int PRIMARY KEY, - customer_name varchar(100), - customer_address varchar(255) + customer_id INT PRIMARY KEY, + customer_name VARCHAR(50) NOT NULL, + customer_address VARCHAR(50) NOT NULL, ); -- Orders from customers. --- Keep in mind that the delivery_date may be NULL CREATE TABLE IF NOT EXISTS orders ( - order_id int PRIMARY KEY, - customer_id int, - order_date date DEFAULT NOW, - delivery_date date, -- Set when the order hits the truck - FOREIGN KEY (customer_id) REFERENCES customers(customer_id) + order_id INT PRIMARY KEY, + customer_id INT NOT NULL, + cookie_id INT NOT NULL, + order_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (order_date >= CURRENT_DATE), + FOREIGN KEY (customer_id) REFERENCES customer(customer_id), + FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) ); -------------------------------------------- -- Recipe/Cookie related tables -------------------------------------------- --- Recipes for all the cookies (essentially a list of cookies) -CREATE TABLE IF NOT EXISTS recipes ( - recipe_id int PRIMARY KEY, - recipe_name varchar(100) UNIQUE -- Cookie name -); - --- "The company has a raw materials warehouse in which --- all ingredients used in their production are stored." - -- Describes ingredients and stock. --- Each ingredient has 'amount' of 'unit' in stock -CREATE TABLE IF NOT EXISTS ingredients ( - ingredient_id int PRIMARY KEY, - ingredient_name varchar(100), - amount int, - unit varchar(50) +CREATE TABLE IF NOT EXISTS raw_materials ( + ingredient_id INT PRIMARY KEY, + ingredient_name VARCHAR(50) NOT NULL UNIQUE, + ingredient_quantity INT NOT NULL, + unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')) ); --- Describes what ingredients goes into what recipe --- Each recipe requires 'amount' of 'ingredient' -CREATE TABLE IF NOT EXISTS recipe_contents ( - recipe_id int, - ingredient_id int, - amount int, - PRIMARY KEY (recipe_id, ingredient_id) +-- When did we get the ingredients? +CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( + delivery_id INT PRIMARY KEY, + ingredient_id INT NOT NULL, + delivery_date DATE NOT NULL, + delivery_quantity INT NOT NULL, + unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')), + FOREIGN KEY (ingredient_id) REFERENCES raw_materials(ingredient_id) ); -------------------------------------------- @@ -56,28 +47,20 @@ CREATE TABLE IF NOT EXISTS recipe_contents ( -- Pallets are used to store cookies for delivery CREATE TABLE IF NOT EXISTS pallets ( - pallet_id int PRIMARY KEY, - recipe_id int, - order_id int, - FOREIGN KEY (recipe_id) REFERENCES recipes(recipe_id), - FOREIGN KEY (order_id) REFERENCES Orders(order_id) -); - --- What does the pallet contain? -CREATE TABLE IF NOT EXISTS pallet_contents ( - pallet_id int, - ingredient_id int, - amount int, - PRIMARY KEY (pallet_id, ingredient_id), - FOREIGN KEY (pallet_id) REFERENCES pallets(pallet_id), - FOREIGN KEY (ingredient_id) REFERENCES ingredients(ingredient_id) + pallet_id INT PRIMARY KEY, + cookie_id INT NOT NULL, + order_id INT NOT NULL, + status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), + production_date DATE NOT NULL, + FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) + FOREIGN KEY (order_id) REFERENCES orders(order_id) ); -- Has an order been delivered? --- When the truck is loaded, a delivery is considered done -CREATE TABLE IF NOT EXISTS delivery_bill ( - delivery_id int PRIMARY KEY, - order_id int, - delivery_date date DEFAULT NOW, - FOREIGN KEY (order_id) REFERENCES Orders(order_id) +CREATE TABLE IF NOT EXISTS deliveries ( + delivery_id INT PRIMARY KEY, + pallet_id INT NOT NULL, + customer_name VARCHAR(50) NOT NULL, + delivery_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (delivery_date >= CURRENT_DATE), + FOREIGN KEY (pallet_id) REFERENCES pallets(pallet_id) ); \ No newline at end of file From cd11effb42447fef0ae5a4373e16a0156eca3dc6 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:34:22 +0200 Subject: [PATCH 09/38] Typo --- app/Migrations/create-schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 20663a8..ead6e9c 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS customers ( customer_id INT PRIMARY KEY, customer_name VARCHAR(50) NOT NULL, - customer_address VARCHAR(50) NOT NULL, + customer_address VARCHAR(50) NOT NULL ); -- Orders from customers. From 844ace05b08cb4d3f48b52d5819225b30f5e5070 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:36:27 +0200 Subject: [PATCH 10/38] Cookie table... --- app/Migrations/create-schema.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index ead6e9c..b0d633a 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -2,6 +2,12 @@ -- Recipe/Cookie related tables -------------------------------------------- +-- Holds the different types of cookies we can make. +CREATE TABLE cookie ( + cookie_id INT PRIMARY KEY, + cookie_name VARCHAR(50) NOT NULL UNIQUE, +); + -- Our known customers, may need more fields CREATE TABLE IF NOT EXISTS customers ( customer_id INT PRIMARY KEY, From 2a834d8a8aec191d518cf00151df106b8b5fb1f5 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:37:25 +0200 Subject: [PATCH 11/38] Correct form of table name --- README.md | 4 ++-- app/Migrations/create-schema.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 375bc08..5f1ac04 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Unsuprisingly, we will need a cookie table. ```sql -CREATE TABLE cookie ( +CREATE TABLE cookies ( cookie_id INT PRIMARY KEY, cookie_name VARCHAR(50) NOT NULL UNIQUE, ); @@ -16,7 +16,7 @@ CREATE TABLE cookie ( Last i checked, a commercial bakery needs customers: ```sql -CREATE TABLE customer ( +CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name VARCHAR(50) NOT NULL, customer_address VARCHAR(50) NOT NULL, diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index b0d633a..d21ad42 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -3,7 +3,7 @@ -------------------------------------------- -- Holds the different types of cookies we can make. -CREATE TABLE cookie ( +CREATE TABLE cookies ( cookie_id INT PRIMARY KEY, cookie_name VARCHAR(50) NOT NULL UNIQUE, ); From 926c8cb06f3d15fc10acc3ac81314ea41f26c6be Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:38:10 +0200 Subject: [PATCH 12/38] Typo --- app/Migrations/create-schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index d21ad42..f98f253 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -5,7 +5,7 @@ -- Holds the different types of cookies we can make. CREATE TABLE cookies ( cookie_id INT PRIMARY KEY, - cookie_name VARCHAR(50) NOT NULL UNIQUE, + cookie_name VARCHAR(50) NOT NULL UNIQUE ); -- Our known customers, may need more fields From 25f5dd151d9555587722b967888e11a7dd58d761 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:40:12 +0200 Subject: [PATCH 13/38] Name changes --- app/Migrations/initial-data.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Migrations/initial-data.sql b/app/Migrations/initial-data.sql index 1fd9348..25de406 100644 --- a/app/Migrations/initial-data.sql +++ b/app/Migrations/initial-data.sql @@ -12,7 +12,7 @@ VALUES (8, 'Småbröd AB', 'Malmö'); INSERT -OR IGNORE INTO recipes (recipe_name) +OR IGNORE INTO cookies (cookie_name) VALUES ('Nut ring'), ('Nut cookie'), @@ -22,7 +22,7 @@ VALUES ('Berliner'); INSERT -OR IGNORE INTO ingredients (ingredient_name, amount, unit) +OR IGNORE INTO raw_materials(ingredient_name, ingredient_quantity, unit) VALUES ('Bread crumbs', 500000, 'g'), ('Butter', 500000, 'g'), From d52e1507d7fd01e750fd33237a40854d24e2a73d Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:40:31 +0200 Subject: [PATCH 14/38] Table names in getCookies --- app/src/main/java/krusty/Database.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index e14a4e5..3873eef 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -40,7 +40,7 @@ public class Database { } public String getCookies(Request req, Response res) { - String result = selectQuery("recipes", "cookies", "recipe_name"); + String result = selectQuery("cookies", "cookies", "recipe_name"); result = result.replaceAll("recipe_name", "name"); return result; } From 884662e40990b0ac8a19b5812fdfb3afea9e3ddf Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:45:38 +0200 Subject: [PATCH 15/38] Renaming in database.java --- app/src/main/java/krusty/Database.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 3873eef..08e80b8 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -34,14 +34,15 @@ public class Database { } public String getRawMaterials(Request req, Response res) { - String result = selectQuery("ingredients", "raw-materials", "ingredient_name", "amount", "unit"); + String result = selectQuery("raw_materials", "raw-materials", "ingredient_name", "ingredient_quantity", "unit"); result = result.replaceAll("ingredient_name", "name"); + result = result.replaceAll("ingredient_quantity", "amount"); return result; } public String getCookies(Request req, Response res) { - String result = selectQuery("cookies", "cookies", "recipe_name"); - result = result.replaceAll("recipe_name", "name"); + String result = selectQuery("cookies", "cookies", "cookie_name"); + result = result.replaceAll("cookie_name", "name"); return result; } From c186c3b5157789744c4792cfae6b8eecc29bf14c Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:52:41 +0200 Subject: [PATCH 16/38] Demo steps --- app/src/main/java/krusty/Database.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 08e80b8..96ea689 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -47,18 +47,32 @@ public class Database { } public String getRecipes(Request req, Response res) { + // Essentially serialize DefaultRecipes to json return "{}"; } public String getPallets(Request req, Response res) { + // 1. Get query param -> validate cookie type if not null + // 2. Figure out if we should return all pallets or just one type + // 3. Return pallets as {cookie, blocked} return "{\"pallets\":[]}"; } public String reset(Request req, Response res) { + // 1. Wipe database + // 2. Re-run migrations + // 3. Return success return "{}"; } public String createPallet(Request req, Response res) { + // 1. Get query param + // 2. Check if cookie exists (is in DefaultRecipes) + // 3. Start transaction + // 3. Check with db if raw materials are available -> decrement if so + // 4. Insert into pallets + // 5. Commit transaction + // 6. Return pallet id return "{}"; } From c8752c388c3c54139422f27b0aa47804117cbd88 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:57:55 +0200 Subject: [PATCH 17/38] Typos --- app/Migrations/create-schema.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index f98f253..20c1aa6 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -3,7 +3,7 @@ -------------------------------------------- -- Holds the different types of cookies we can make. -CREATE TABLE cookies ( +CREATE TABLE IF NOT EXISTS cookies ( cookie_id INT PRIMARY KEY, cookie_name VARCHAR(50) NOT NULL UNIQUE ); @@ -21,8 +21,8 @@ CREATE TABLE IF NOT EXISTS orders ( customer_id INT NOT NULL, cookie_id INT NOT NULL, order_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (order_date >= CURRENT_DATE), - FOREIGN KEY (customer_id) REFERENCES customer(customer_id), - FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) + FOREIGN KEY (customer_id) REFERENCES customers(customer_id), + FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) ); -------------------------------------------- @@ -58,7 +58,7 @@ CREATE TABLE IF NOT EXISTS pallets ( order_id INT NOT NULL, status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), production_date DATE NOT NULL, - FOREIGN KEY (cookie_id) REFERENCES cookie(cookie_id) + FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) FOREIGN KEY (order_id) REFERENCES orders(order_id) ); From fcde7e6f1ea90296a2d4aef48727d7335b9bf502 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 13:58:02 +0200 Subject: [PATCH 18/38] Proper clean target --- makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/makefile b/makefile index d74125e..2d93e37 100644 --- a/makefile +++ b/makefile @@ -8,6 +8,8 @@ run: clean: ./gradlew clean + rm -f *.tar.gz *.tar.gz.minisig + rm -f app/krusty.db test: ./gradlew test From 54939ca99be4e3711ba282b5ae2258980310780e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 14:17:48 +0200 Subject: [PATCH 19/38] Redundant deliveries table removed --- app/Migrations/create-schema.sql | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 20c1aa6..3517751 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -58,15 +58,7 @@ CREATE TABLE IF NOT EXISTS pallets ( order_id INT NOT NULL, status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), production_date DATE NOT NULL, + delivery_date DATE DEFAULT NULL, FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) FOREIGN KEY (order_id) REFERENCES orders(order_id) ); - --- Has an order been delivered? -CREATE TABLE IF NOT EXISTS deliveries ( - delivery_id INT PRIMARY KEY, - pallet_id INT NOT NULL, - customer_name VARCHAR(50) NOT NULL, - delivery_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (delivery_date >= CURRENT_DATE), - FOREIGN KEY (pallet_id) REFERENCES pallets(pallet_id) -); \ No newline at end of file From 0b8a771aa59984503de48e3ea8818caec00a2f1b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sat, 4 May 2024 14:18:00 +0200 Subject: [PATCH 20/38] Migrate now deletes db... --- makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/makefile b/makefile index 2d93e37..a6ea063 100644 --- a/makefile +++ b/makefile @@ -18,6 +18,7 @@ dbdump: sqlite3 app/krusty.db .dump migrate: + rm -f app/krusty.db sqlite3 app/krusty.db < app/Migrations/create-schema.sql sqlite3 app/krusty.db < app/Migrations/initial-data.sql From 35607ce341bc06e551faa6491ca79ffc3416766e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 06:47:56 +0200 Subject: [PATCH 21/38] getPallets seemingly working --- app/src/main/java/krusty/Database.java | 118 ++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 96ea689..609843c 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -8,9 +8,12 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.text.SimpleDateFormat; // Likely dependencies for general operations import java.io.IOException; import java.sql.ResultSet; +import java.util.Date; +import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; import java.nio.file.Files; @@ -18,12 +21,16 @@ import java.nio.file.Paths; import java.util.stream.Stream; public class Database { + // Here, we use an in-memory database. This string could be changed to // "jdbc:sqlite:.sqlite3" to use a file-based database instead. // Nore that ":memory:" is an **SQLite specific** magic string that tells the // underlying SQLite engine to store the database in memory. private static final String jdbcString = "jdbc:sqlite::memory:"; // private static final String jdbcString = "jdbc:sqlite:krusty.db"; + + // Hold a single connection to the database. Note that this is + // not a pool, so this is not thread-safe nor efficient. private Connection conn = null; public String getCustomers(Request req, Response res) { @@ -52,9 +59,114 @@ public class Database { } public String getPallets(Request req, Response res) { - // 1. Get query param -> validate cookie type if not null - // 2. Figure out if we should return all pallets or just one type - // 3. Return pallets as {cookie, blocked} + // These queries look like: + // http://localhost:8888/api/v1/pallets?cookie=Nut+cookie&from=2024-05-23&to=2024-05-30&blocked=yes + // They may contain any combination of the parameters, or none at all. + + Optional r = Optional.empty(); // Holds the ecipe if we have a cookie + Optional blocked = Optional.empty(); // Holds the blocked status + Optional from = Optional.empty(); // Holds the from date + Optional to = Optional.empty(); // Holds the to date + + // First we need the cookie parameter + String cookie = req.queryParams("cookie"); + + // And the blocked parameter + String blocked_str = req.queryParams("blocked"); + + // Then we need the date parameters + String from_str = req.queryParams("from"); + String to_str = req.queryParams("to"); + + // Date from = null; + // Date to = null; + + // Fancy functional one-liner to get the recipe if the cookie is present + if (cookie != null) { + r = Optional.ofNullable(DefaultRecipes.recipes.stream() + .filter(recipe -> recipe.name.equals(cookie)) + .findFirst().orElse(null)); + } + + if (blocked_str != null) { + blocked = switch (blocked_str) { + case "yes" -> Optional.of(true); + case "no" -> Optional.of(false); + default -> Optional.empty(); + }; + } + + // Both of these must be present + if (from_str != null && to_str != null) { + try { + // Parse both in the format (2024-05-23), also called ISO 8601 + from = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(from_str)); + to = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(to_str)); + } catch (Exception e) { + // Reset the dates to empty + from = Optional.empty(); + to = Optional.empty(); + // We have a bad date, maybe log this somewhere + } + + // Check so that the dates are in the correct order + if (from.isPresent() && to.isPresent() && from.get().after(to.get())) { + // We have a bad interval, perhaps set dates to empty agian? + // TODO: This obviously need louder error handling + from = Optional.empty(); + to = Optional.empty(); + } + } + + // This type of code is unreadable, error prone and hard to maintain. + // The fact that im responsible for this code makes my soul hurt. + // This part almost made me write a simple query factory to handle this. + // + // SqlBuilder exists to 'take the pain out of generating SQL queries', + // but it's not in the standard library. + // + // Helmets, seatbelts and safety goggles on; we need to execute a query. + try { + Statement stmt = conn.createStatement(); + StringBuilder query = new StringBuilder("SELECT * FROM pallets JOIN cookies ON pallets.cookie_id = cookies.cookie_id"); + + // r is validated here + if (r.isPresent()) { + query.append(" WHERE cookie_name = '" + r.get().name + "'"); + } + + if (from != null && to != null) { + if (r.isPresent()) { + query.append(" AND "); + } else { + query.append(" WHERE "); + } + + query.append("production_date BETWEEN '" + from_str + "' AND '" + to_str + "'"); + } + + if (blocked.isPresent()) { + // WARNING THIS IS NOT CORRECT WARNING + if (r.isPresent() || from != null) { + query.append(" AND "); + } + + // TODO: WARNING This logic is flawed. WARNING + // Remember, status can be 'freezer', 'delivered' or 'blocked' + query.append("status = " + (blocked.get() ? "'blocked'" : "'freezer'")); + } + + query.append(";"); + ResultSet result = stmt.executeQuery(query.toString()); + String jsonResult = Jsonizer.toJson(result, "pallets"); + return jsonResult; + } catch (SQLException e) { + System.out.printf("Error executing query: \n%s", e); + } + + // Statue 500, to give the client a + // chance to figure out that something went wrong + res.status(500); return "{\"pallets\":[]}"; } From 71687c8bf70da26253e95cca63e518ee01e5bfc8 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 07:25:10 +0200 Subject: [PATCH 22/38] Uncomment orders --- app/Migrations/create-schema.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 3517751..4ff9482 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -52,13 +52,14 @@ CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( -------------------------------------------- -- Pallets are used to store cookies for delivery +-- Order related columns are unused for now. CREATE TABLE IF NOT EXISTS pallets ( pallet_id INT PRIMARY KEY, cookie_id INT NOT NULL, - order_id INT NOT NULL, + -- order_id INT NOT NULL, status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), production_date DATE NOT NULL, delivery_date DATE DEFAULT NULL, FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) - FOREIGN KEY (order_id) REFERENCES orders(order_id) + -- FOREIGN KEY (order_id) REFERENCES orders(order_id) ); From 9843a078ea38912660552b090a34a11dd920c634 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 07:25:19 +0200 Subject: [PATCH 23/38] More tests passing --- app/src/main/java/krusty/Database.java | 67 +++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 609843c..7a8fed9 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -6,6 +6,7 @@ import spark.Response; // Likely dependencies for db operations import java.sql.Connection; import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.text.SimpleDateFormat; @@ -29,7 +30,7 @@ public class Database { private static final String jdbcString = "jdbc:sqlite::memory:"; // private static final String jdbcString = "jdbc:sqlite:krusty.db"; - // Hold a single connection to the database. Note that this is + // Hold a single connection to the database. Note that this is // not a pool, so this is not thread-safe nor efficient. private Connection conn = null; @@ -128,7 +129,8 @@ public class Database { // Helmets, seatbelts and safety goggles on; we need to execute a query. try { Statement stmt = conn.createStatement(); - StringBuilder query = new StringBuilder("SELECT * FROM pallets JOIN cookies ON pallets.cookie_id = cookies.cookie_id"); + StringBuilder query = new StringBuilder( + "SELECT * FROM pallets JOIN cookies ON pallets.cookie_id = cookies.cookie_id"); // r is validated here if (r.isPresent()) { @@ -178,6 +180,67 @@ public class Database { } public String createPallet(Request req, Response res) { + // This on only has one query param and looks like: + // http://localhost:8888/api/v1/pallets?cookie=Amneris + + Optional r = Optional.empty(); + String cookie = req.queryParams("cookie"); + + if (cookie != null) { + r = Optional.ofNullable(DefaultRecipes.recipes.stream() + .filter(recipe -> recipe.name.equals(cookie)) + .findFirst().orElse(null)); + } + + if (r.isEmpty()) { + // Return 404 + res.status(404); + return "{}"; + } + + try (PreparedStatement getRawMaterials = conn.prepareStatement("SELECT * FROM raw_materials WHERE ingredient_name = ?"); + PreparedStatement decrementRawMaterials = conn.prepareStatement("UPDATE raw_materials SET ingredient_quantity = ingredient_quantity - ? WHERE ingredient_name = ?"); + PreparedStatement insertPallet = conn.prepareStatement("INSERT INTO pallets (cookie_id, production_date, status) VALUES (?, ?, ?)")) { + // Start transaction + conn.setAutoCommit(false); + + for(Ingredient i : r.get().ingredients) { + getRawMaterials.setString(1, i.name); + ResultSet result = getRawMaterials.executeQuery(); + if (!result.next()) { + // Rollback transaction + conn.rollback(); + // Return 500 + res.status(500); + return "{}"; + } + + // Check if we have enough raw materials + if (result.getInt("ingredient_quantity") < i.amount) { + // Rollback transaction + conn.rollback(); + // Return 500 + res.status(500); + return "{}"; + } + + decrementRawMaterials.setInt(1, i.amount * 54); // 5400 / 100 + decrementRawMaterials.setString(2, i.name); + decrementRawMaterials.executeUpdate(); + } + + insertPallet.setInt(1, 1); + insertPallet.setString(2, new SimpleDateFormat("yyyy-MM-dd").format(new Date())); + insertPallet.setString(3, "freezer"); + + insertPallet.executeUpdate(); + conn.commit(); + } catch (SQLException e) { + System.out.printf("Error starting transaction: \n%s", e); + } + + // TODO: NOT DONE + // 1. Get query param // 2. Check if cookie exists (is in DefaultRecipes) // 3. Start transaction From 8c8584b6bffb35e75a17f126c0c93a664578f096 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 09:37:33 +0200 Subject: [PATCH 24/38] DB script fix. INT is not the same as INTEGER, apparently --- app/Migrations/create-schema.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 4ff9482..a5c85a1 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -4,20 +4,20 @@ -- Holds the different types of cookies we can make. CREATE TABLE IF NOT EXISTS cookies ( - cookie_id INT PRIMARY KEY, + cookie_id INTEGER PRIMARY KEY, cookie_name VARCHAR(50) NOT NULL UNIQUE ); -- Our known customers, may need more fields CREATE TABLE IF NOT EXISTS customers ( - customer_id INT PRIMARY KEY, + customer_id INTEGER PRIMARY KEY, customer_name VARCHAR(50) NOT NULL, customer_address VARCHAR(50) NOT NULL ); -- Orders from customers. CREATE TABLE IF NOT EXISTS orders ( - order_id INT PRIMARY KEY, + order_id INTEGER PRIMARY KEY, customer_id INT NOT NULL, cookie_id INT NOT NULL, order_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (order_date >= CURRENT_DATE), @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS orders ( -- Describes ingredients and stock. CREATE TABLE IF NOT EXISTS raw_materials ( - ingredient_id INT PRIMARY KEY, + ingredient_id INTEGER PRIMARY KEY, ingredient_name VARCHAR(50) NOT NULL UNIQUE, ingredient_quantity INT NOT NULL, unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')) @@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS raw_materials ( -- When did we get the ingredients? CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( - delivery_id INT PRIMARY KEY, + delivery_id INTEGER PRIMARY KEY, ingredient_id INT NOT NULL, delivery_date DATE NOT NULL, delivery_quantity INT NOT NULL, @@ -54,7 +54,7 @@ CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( -- Pallets are used to store cookies for delivery -- Order related columns are unused for now. CREATE TABLE IF NOT EXISTS pallets ( - pallet_id INT PRIMARY KEY, + pallet_id INTEGER PRIMARY KEY, cookie_id INT NOT NULL, -- order_id INT NOT NULL, status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), From 7d21a572af427b41fe885bab04c2749772245c90 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 10:44:07 +0200 Subject: [PATCH 25/38] Helper tostring in recipe for easier debugging --- app/src/main/java/krusty/Recipe.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/krusty/Recipe.java b/app/src/main/java/krusty/Recipe.java index 864e39a..beb56de 100644 --- a/app/src/main/java/krusty/Recipe.java +++ b/app/src/main/java/krusty/Recipe.java @@ -10,6 +10,12 @@ public class Recipe { } public String toString() { - return name; + StringBuilder sb = new StringBuilder(name + ": "); + + for (Ingredient i : ingredients) { + sb.append(i.toString()); + sb.append(" "); + } + return sb.toString(); } } \ No newline at end of file From 19b6b05b69b27524d2f7c3df4cc63fcf282001f6 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 10:44:28 +0200 Subject: [PATCH 26/38] Dropping entire database every migration --- app/Migrations/create-schema.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index a5c85a1..b0aee90 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -1,3 +1,13 @@ +PRAGMA foreign_keys = OFF; + +-- Drop everything... +DROP TABLE IF EXISTS pallets; +DROP TABLE IF EXISTS raw_materials_deliveries; +DROP TABLE IF EXISTS raw_materials; +DROP TABLE IF EXISTS orders; +DROP TABLE IF EXISTS customers; +DROP TABLE IF EXISTS cookies; + -------------------------------------------- -- Recipe/Cookie related tables -------------------------------------------- @@ -63,3 +73,5 @@ CREATE TABLE IF NOT EXISTS pallets ( FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) -- FOREIGN KEY (order_id) REFERENCES orders(order_id) ); + +PRAGMA foreign_keys = ON; \ No newline at end of file From 4aeb738df375f1a7c1d82179c0a64eb3516d516a Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 10:44:37 +0200 Subject: [PATCH 27/38] Passing all but one test --- app/src/main/java/krusty/Database.java | 87 ++++++++++++++++++-------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 7a8fed9..84df098 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -27,8 +27,8 @@ public class Database { // "jdbc:sqlite:.sqlite3" to use a file-based database instead. // Nore that ":memory:" is an **SQLite specific** magic string that tells the // underlying SQLite engine to store the database in memory. - private static final String jdbcString = "jdbc:sqlite::memory:"; - // private static final String jdbcString = "jdbc:sqlite:krusty.db"; + // private static final String jdbcString = "jdbc:sqlite::memory:"; + private static final String jdbcString = "jdbc:sqlite:krusty.db"; // Hold a single connection to the database. Note that this is // not a pool, so this is not thread-safe nor efficient. @@ -79,9 +79,6 @@ public class Database { String from_str = req.queryParams("from"); String to_str = req.queryParams("to"); - // Date from = null; - // Date to = null; - // Fancy functional one-liner to get the recipe if the cookie is present if (cookie != null) { r = Optional.ofNullable(DefaultRecipes.recipes.stream() @@ -130,28 +127,28 @@ public class Database { try { Statement stmt = conn.createStatement(); StringBuilder query = new StringBuilder( - "SELECT * FROM pallets JOIN cookies ON pallets.cookie_id = cookies.cookie_id"); + "SELECT cookie_name, status FROM pallets JOIN cookies ON pallets.cookie_id = cookies.cookie_id"); // r is validated here if (r.isPresent()) { query.append(" WHERE cookie_name = '" + r.get().name + "'"); } - if (from != null && to != null) { - if (r.isPresent()) { - query.append(" AND "); - } else { - query.append(" WHERE "); - } + // If both from and to are present + if (from.isPresent() && to.isPresent()) { + String query_from = new SimpleDateFormat("yyyy-MM-dd").format(from.get()); + String query_to = new SimpleDateFormat("yyyy-MM-dd").format(to.get()); - query.append("production_date BETWEEN '" + from_str + "' AND '" + to_str + "'"); + // Super hacky, low quality code + String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; + + query.append(clause + "production_date BETWEEN '" + query_from + "' AND '" + query_to + "'"); } - if (blocked.isPresent()) { - // WARNING THIS IS NOT CORRECT WARNING - if (r.isPresent() || from != null) { - query.append(" AND "); - } + if(blocked.isPresent()) { + // This again + String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; + query.append(clause); // TODO: WARNING This logic is flawed. WARNING // Remember, status can be 'freezer', 'delivered' or 'blocked' @@ -159,8 +156,21 @@ public class Database { } query.append(";"); + + System.out.println(query.toString()); + ResultSet result = stmt.executeQuery(query.toString()); + + // Rename the columns String jsonResult = Jsonizer.toJson(result, "pallets"); + + // Some carmack level code, as usual + jsonResult = jsonResult.replaceAll("cookie_name", "cookie"); + jsonResult = jsonResult.replaceAll("freezer", "no"); + jsonResult = jsonResult.replaceAll("delivered", "no"); + jsonResult = jsonResult.replaceAll("blocked", "yes"); + jsonResult = jsonResult.replaceAll("status", "blocked"); + return jsonResult; } catch (SQLException e) { System.out.printf("Error executing query: \n%s", e); @@ -173,9 +183,14 @@ public class Database { } public String reset(Request req, Response res) { - // 1. Wipe database - // 2. Re-run migrations - // 3. Return success + try { + this.migrateScript("Migrations/create-schema.sql"); + this.migrateScript("Migrations/initial-data.sql"); + } catch (Exception e) { + System.out.printf("Error resetting database: \n%s", e); + res.status(500); + return "{}"; + } return "{}"; } @@ -198,13 +213,20 @@ public class Database { return "{}"; } - try (PreparedStatement getRawMaterials = conn.prepareStatement("SELECT * FROM raw_materials WHERE ingredient_name = ?"); - PreparedStatement decrementRawMaterials = conn.prepareStatement("UPDATE raw_materials SET ingredient_quantity = ingredient_quantity - ? WHERE ingredient_name = ?"); - PreparedStatement insertPallet = conn.prepareStatement("INSERT INTO pallets (cookie_id, production_date, status) VALUES (?, ?, ?)")) { + // System.out.println(r.get()); + + try (PreparedStatement getRawMaterials = conn + .prepareStatement("SELECT * FROM raw_materials WHERE ingredient_name = ?"); + PreparedStatement decrementRawMaterials = conn.prepareStatement( + "UPDATE raw_materials SET ingredient_quantity = ingredient_quantity - ? WHERE ingredient_name = ?"); + PreparedStatement insertPallet = conn.prepareStatement( + "INSERT INTO pallets (cookie_id, production_date, status) VALUES (?, ?, ?)"); + PreparedStatement getCookieId = conn + .prepareStatement("SELECT cookie_id FROM cookies WHERE cookie_name = ?")) { // Start transaction conn.setAutoCommit(false); - for(Ingredient i : r.get().ingredients) { + for (Ingredient i : r.get().ingredients) { getRawMaterials.setString(1, i.name); ResultSet result = getRawMaterials.executeQuery(); if (!result.next()) { @@ -229,7 +251,20 @@ public class Database { decrementRawMaterials.executeUpdate(); } - insertPallet.setInt(1, 1); + // Fish out the cookie id + getCookieId.setString(1, cookie); + ResultSet cookie_rs = getCookieId.executeQuery(); + if (!cookie_rs.next()) { + // Rollback transaction + conn.rollback(); + // Return 500 + res.status(500); + return "{}"; + } + + int cookie_id = cookie_rs.getInt("cookie_id"); + + insertPallet.setInt(1, cookie_id); insertPallet.setString(2, new SimpleDateFormat("yyyy-MM-dd").format(new Date())); insertPallet.setString(3, "freezer"); From 6554cb2b6f9e440734c50a0d0721d64d4ac803df Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 10:52:13 +0200 Subject: [PATCH 28/38] All tests passing --- app/src/main/java/krusty/Database.java | 52 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 84df098..8ccc97f 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -27,8 +27,8 @@ public class Database { // "jdbc:sqlite:.sqlite3" to use a file-based database instead. // Nore that ":memory:" is an **SQLite specific** magic string that tells the // underlying SQLite engine to store the database in memory. - // private static final String jdbcString = "jdbc:sqlite::memory:"; - private static final String jdbcString = "jdbc:sqlite:krusty.db"; + private static final String jdbcString = "jdbc:sqlite::memory:"; + // private static final String jdbcString = "jdbc:sqlite:krusty.db"; // Hold a single connection to the database. Note that this is // not a pool, so this is not thread-safe nor efficient. @@ -94,26 +94,26 @@ public class Database { }; } - // Both of these must be present - if (from_str != null && to_str != null) { + if(from_str != null) { try { - // Parse both in the format (2024-05-23), also called ISO 8601 from = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(from_str)); + } catch (Exception e) { + from = Optional.empty(); + } + } + + if(to_str != null) { + try { to = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(to_str)); } catch (Exception e) { - // Reset the dates to empty - from = Optional.empty(); to = Optional.empty(); - // We have a bad date, maybe log this somewhere } + } - // Check so that the dates are in the correct order - if (from.isPresent() && to.isPresent() && from.get().after(to.get())) { - // We have a bad interval, perhaps set dates to empty agian? - // TODO: This obviously need louder error handling - from = Optional.empty(); - to = Optional.empty(); - } + // If the interval is negative, reset the dates + if(from.isPresent() && to.isPresent() && from.get().after(to.get())) { + from = Optional.empty(); + to = Optional.empty(); } // This type of code is unreadable, error prone and hard to maintain. @@ -135,16 +135,34 @@ public class Database { } // If both from and to are present - if (from.isPresent() && to.isPresent()) { + if (from.isPresent()) { String query_from = new SimpleDateFormat("yyyy-MM-dd").format(from.get()); + + // Super hacky, low quality code + String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; + + query.append(clause + "production_date >= '" + query_from + "'"); + } + + if(to.isPresent()) { String query_to = new SimpleDateFormat("yyyy-MM-dd").format(to.get()); // Super hacky, low quality code String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; - query.append(clause + "production_date BETWEEN '" + query_from + "' AND '" + query_to + "'"); + query.append(clause + "production_date <= '" + query_to + "'"); } + // if (from.isPresent() && to.isPresent()) { + // String query_from = new SimpleDateFormat("yyyy-MM-dd").format(from.get()); + // String query_to = new SimpleDateFormat("yyyy-MM-dd").format(to.get()); + + // // Super hacky, low quality code + // String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; + + // query.append(clause + "production_date BETWEEN '" + query_from + "' AND '" + query_to + "'"); + // } + if(blocked.isPresent()) { // This again String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; From 09765fdb24583884a76c5eed0af4befa83fff77f Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 11:39:28 +0200 Subject: [PATCH 29/38] Some more tables and using views in query --- app/Migrations/create-schema.sql | 41 ++++++++++++++++++++++++-- app/src/main/java/krusty/Database.java | 2 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index b0aee90..5c6664a 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -38,6 +38,27 @@ CREATE TABLE IF NOT EXISTS orders ( -------------------------------------------- -- Recipe/Cookie related tables -------------------------------------------- +-- Notes: the unit type can be defined in terms +-- of volume or weight instead. Here we choose +-- to use static si-prefixes in relevant tables. + +-- What types of ingredients do we handle +CREATE TABLE IF NOT EXISTS ingredients ( + ingredient_id INTEGER PRIMARY KEY, + ingredient_name VARCHAR(50) NOT NULL UNIQUE, + preferred_unit VARCHAR(50) NOT NULL CHECK (preferred_unit IN ('g', 'ml')) +); + +-- What ingredients are in what cookies +CREATE TABLE IF NOT EXISTS recipe_contents ( + cookie_id INT NOT NULL, + ingredient_id INT NOT NULL, + quantity INT NOT NULL, + unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')), + PRIMARY KEY (cookie_id, ingredient_id), + FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id), + FOREIGN KEY (ingredient_id) REFERENCES ingredients(ingredient_id) +); -- Describes ingredients and stock. CREATE TABLE IF NOT EXISTS raw_materials ( @@ -66,12 +87,28 @@ CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( CREATE TABLE IF NOT EXISTS pallets ( pallet_id INTEGER PRIMARY KEY, cookie_id INT NOT NULL, - -- order_id INT NOT NULL, + order_id INT, -- This should be not null status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), production_date DATE NOT NULL, delivery_date DATE DEFAULT NULL, FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) - -- FOREIGN KEY (order_id) REFERENCES orders(order_id) + FOREIGN KEY (order_id) REFERENCES orders(order_id) ); +-------------------------------------------- +-- Views +-------------------------------------------- + +-- Pallet +CREATE VIEW IF NOT EXISTS pallets_view AS +SELECT + pallet_id, + cookie_name, + order_id, + status, + production_date, + delivery_date +FROM pallets +JOIN cookies ON pallets.cookie_id = cookies.cookie_id; + PRAGMA foreign_keys = ON; \ No newline at end of file diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 8ccc97f..67c1a93 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -127,7 +127,7 @@ public class Database { try { Statement stmt = conn.createStatement(); StringBuilder query = new StringBuilder( - "SELECT cookie_name, status FROM pallets JOIN cookies ON pallets.cookie_id = cookies.cookie_id"); + "SELECT cookie_name, status FROM pallets_view"); // r is validated here if (r.isPresent()) { From 72d03800238775e565f53f24d096df2e9ad271e6 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 11:47:26 +0200 Subject: [PATCH 30/38] Cleaning --- app/src/main/java/krusty/Database.java | 124 ++++++++++--------------- 1 file changed, 49 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 67c1a93..6c4a6dc 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -69,53 +69,56 @@ public class Database { Optional from = Optional.empty(); // Holds the from date Optional to = Optional.empty(); // Holds the to date - // First we need the cookie parameter - String cookie = req.queryParams("cookie"); + // Parameter validation block + { + // First we need the cookie parameter + String cookie = req.queryParams("cookie"); - // And the blocked parameter - String blocked_str = req.queryParams("blocked"); + // And the blocked parameter + String blocked_str = req.queryParams("blocked"); - // Then we need the date parameters - String from_str = req.queryParams("from"); - String to_str = req.queryParams("to"); + // Then we need the date parameters + String from_str = req.queryParams("from"); + String to_str = req.queryParams("to"); - // Fancy functional one-liner to get the recipe if the cookie is present - if (cookie != null) { - r = Optional.ofNullable(DefaultRecipes.recipes.stream() - .filter(recipe -> recipe.name.equals(cookie)) - .findFirst().orElse(null)); - } - - if (blocked_str != null) { - blocked = switch (blocked_str) { - case "yes" -> Optional.of(true); - case "no" -> Optional.of(false); - default -> Optional.empty(); - }; - } - - if(from_str != null) { - try { - from = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(from_str)); - } catch (Exception e) { - from = Optional.empty(); + // Fancy functional one-liner to get the recipe if the cookie is present + if (cookie != null) { + r = Optional.ofNullable(DefaultRecipes.recipes.stream() + .filter(recipe -> recipe.name.equals(cookie)) + .findFirst().orElse(null)); } - } - if(to_str != null) { - try { - to = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(to_str)); - } catch (Exception e) { + if (blocked_str != null) { + blocked = switch (blocked_str) { + case "yes" -> Optional.of(true); + case "no" -> Optional.of(false); + default -> Optional.empty(); + }; + } + + if (from_str != null) { + try { + from = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(from_str)); + } catch (Exception e) { + from = Optional.empty(); + } + } + + if (to_str != null) { + try { + to = Optional.of(new SimpleDateFormat("yyyy-MM-dd").parse(to_str)); + } catch (Exception e) { + to = Optional.empty(); + } + } + + // If the interval is negative, reset the dates + if (from.isPresent() && to.isPresent() && from.get().after(to.get())) { + from = Optional.empty(); to = Optional.empty(); } } - // If the interval is negative, reset the dates - if(from.isPresent() && to.isPresent() && from.get().after(to.get())) { - from = Optional.empty(); - to = Optional.empty(); - } - // This type of code is unreadable, error prone and hard to maintain. // The fact that im responsible for this code makes my soul hurt. // This part almost made me write a simple query factory to handle this. @@ -134,7 +137,6 @@ public class Database { query.append(" WHERE cookie_name = '" + r.get().name + "'"); } - // If both from and to are present if (from.isPresent()) { String query_from = new SimpleDateFormat("yyyy-MM-dd").format(from.get()); @@ -144,7 +146,7 @@ public class Database { query.append(clause + "production_date >= '" + query_from + "'"); } - if(to.isPresent()) { + if (to.isPresent()) { String query_to = new SimpleDateFormat("yyyy-MM-dd").format(to.get()); // Super hacky, low quality code @@ -153,17 +155,7 @@ public class Database { query.append(clause + "production_date <= '" + query_to + "'"); } - // if (from.isPresent() && to.isPresent()) { - // String query_from = new SimpleDateFormat("yyyy-MM-dd").format(from.get()); - // String query_to = new SimpleDateFormat("yyyy-MM-dd").format(to.get()); - - // // Super hacky, low quality code - // String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; - - // query.append(clause + "production_date BETWEEN '" + query_from + "' AND '" + query_to + "'"); - // } - - if(blocked.isPresent()) { + if (blocked.isPresent()) { // This again String clause = query.toString().contains("WHERE") ? " AND " : " WHERE "; query.append(clause); @@ -173,10 +165,6 @@ public class Database { query.append("status = " + (blocked.get() ? "'blocked'" : "'freezer'")); } - query.append(";"); - - System.out.println(query.toString()); - ResultSet result = stmt.executeQuery(query.toString()); // Rename the columns @@ -226,13 +214,10 @@ public class Database { } if (r.isEmpty()) { - // Return 404 res.status(404); return "{}"; } - // System.out.println(r.get()); - try (PreparedStatement getRawMaterials = conn .prepareStatement("SELECT * FROM raw_materials WHERE ingredient_name = ?"); PreparedStatement decrementRawMaterials = conn.prepareStatement( @@ -248,23 +233,21 @@ public class Database { getRawMaterials.setString(1, i.name); ResultSet result = getRawMaterials.executeQuery(); if (!result.next()) { - // Rollback transaction conn.rollback(); - // Return 500 res.status(500); return "{}"; } + int amount_per_pallet = i.amount * 54; // 54 * 100 + // Check if we have enough raw materials - if (result.getInt("ingredient_quantity") < i.amount) { - // Rollback transaction + if (result.getInt("ingredient_quantity") < amount_per_pallet) { conn.rollback(); - // Return 500 res.status(500); return "{}"; } - decrementRawMaterials.setInt(1, i.amount * 54); // 5400 / 100 + decrementRawMaterials.setInt(1, amount_per_pallet); decrementRawMaterials.setString(2, i.name); decrementRawMaterials.executeUpdate(); } @@ -273,9 +256,7 @@ public class Database { getCookieId.setString(1, cookie); ResultSet cookie_rs = getCookieId.executeQuery(); if (!cookie_rs.next()) { - // Rollback transaction conn.rollback(); - // Return 500 res.status(500); return "{}"; } @@ -292,15 +273,7 @@ public class Database { System.out.printf("Error starting transaction: \n%s", e); } - // TODO: NOT DONE - - // 1. Get query param - // 2. Check if cookie exists (is in DefaultRecipes) - // 3. Start transaction - // 3. Check with db if raw materials are available -> decrement if so - // 4. Insert into pallets - // 5. Commit transaction - // 6. Return pallet id + res.status(201); return "{}"; } @@ -347,6 +320,7 @@ public class Database { // The script location is relative to the gradle // build script ("build.gradle.kts", in this case). + // Assumes every statement ends with a semicolon. (notably broken for triggers) /** Reads an sql script into the database */ public void migrateScript(String filename) throws IOException, SQLException { try (Stream lines = Files.lines(Paths.get(filename))) { From 1969d2f98f1195ef653eb77ba9de3f94a63e8e65 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 11:52:55 +0200 Subject: [PATCH 31/38] Some more cleaning in tables --- app/Migrations/create-schema.sql | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 5c6664a..57c565d 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -36,20 +36,22 @@ CREATE TABLE IF NOT EXISTS orders ( ); -------------------------------------------- --- Recipe/Cookie related tables +-- Raw materials, ingredients and recipes -------------------------------------------- -- Notes: the unit type can be defined in terms -- of volume or weight instead. Here we choose -- to use static si-prefixes in relevant tables. --- What types of ingredients do we handle +-- What types of ingredients do we handle. +-- Not currently used, but kept as an example. CREATE TABLE IF NOT EXISTS ingredients ( ingredient_id INTEGER PRIMARY KEY, ingredient_name VARCHAR(50) NOT NULL UNIQUE, preferred_unit VARCHAR(50) NOT NULL CHECK (preferred_unit IN ('g', 'ml')) ); --- What ingredients are in what cookies +-- What ingredients are in what cookies? +-- Glues together the cookies and ingredients, a 'recipe'. CREATE TABLE IF NOT EXISTS recipe_contents ( cookie_id INT NOT NULL, ingredient_id INT NOT NULL, @@ -61,6 +63,7 @@ CREATE TABLE IF NOT EXISTS recipe_contents ( ); -- Describes ingredients and stock. +-- This should reference the ingredients table, but we'll keep it simple for now. CREATE TABLE IF NOT EXISTS raw_materials ( ingredient_id INTEGER PRIMARY KEY, ingredient_name VARCHAR(50) NOT NULL UNIQUE, From b4b12b31a2c90e1ebd5a80a9726b7ed58f8802de Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 12:51:01 +0200 Subject: [PATCH 32/38] New table for deliveries --- app/Migrations/create-schema.sql | 18 ++++++++++++++---- app/src/main/java/krusty/Database.java | 4 ++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 57c565d..28d1d15 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -93,11 +93,20 @@ CREATE TABLE IF NOT EXISTS pallets ( order_id INT, -- This should be not null status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), production_date DATE NOT NULL, - delivery_date DATE DEFAULT NULL, FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) FOREIGN KEY (order_id) REFERENCES orders(order_id) ); +-- Connects pallets to orders +CREATE TABLE IF NOT EXISTS deliveries ( + delivery_date DATE DEFAULT NOW, + order_id INT NOT NULL, + pallet_id INT NOT NULL, + FOREIGN KEY (order_id) REFERENCES orders(order_id), + FOREIGN KEY (pallet_id) REFERENCES pallets(pallet_id), + PRIMARY KEY (order_id, pallet_id) +); + -------------------------------------------- -- Views -------------------------------------------- @@ -105,13 +114,14 @@ CREATE TABLE IF NOT EXISTS pallets ( -- Pallet CREATE VIEW IF NOT EXISTS pallets_view AS SELECT - pallet_id, + pallets.pallet_id, cookie_name, - order_id, + pallets.order_id, status, production_date, delivery_date FROM pallets -JOIN cookies ON pallets.cookie_id = cookies.cookie_id; +LEFT JOIN cookies ON pallets.cookie_id = cookies.cookie_id +LEFT JOIN deliveries ON pallets.pallet_id = deliveries.pallet_id; PRAGMA foreign_keys = ON; \ No newline at end of file diff --git a/app/src/main/java/krusty/Database.java b/app/src/main/java/krusty/Database.java index 6c4a6dc..6f59bbf 100644 --- a/app/src/main/java/krusty/Database.java +++ b/app/src/main/java/krusty/Database.java @@ -165,6 +165,8 @@ public class Database { query.append("status = " + (blocked.get() ? "'blocked'" : "'freezer'")); } + System.out.println(query.toString()); + ResultSet result = stmt.executeQuery(query.toString()); // Rename the columns @@ -267,6 +269,8 @@ public class Database { insertPallet.setString(2, new SimpleDateFormat("yyyy-MM-dd").format(new Date())); insertPallet.setString(3, "freezer"); + System.out.println(insertPallet.toString()); + insertPallet.executeUpdate(); conn.commit(); } catch (SQLException e) { From d0f2bd944d2a6d583faf49c61c97fcf78996a015 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 13:25:49 +0200 Subject: [PATCH 33/38] Final touches to sql --- app/Migrations/create-schema.sql | 69 +++++++++++++++----------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/app/Migrations/create-schema.sql b/app/Migrations/create-schema.sql index 28d1d15..d5561ef 100644 --- a/app/Migrations/create-schema.sql +++ b/app/Migrations/create-schema.sql @@ -9,15 +9,9 @@ DROP TABLE IF EXISTS customers; DROP TABLE IF EXISTS cookies; -------------------------------------------- --- Recipe/Cookie related tables +-- Orders, deliveries and customers -------------------------------------------- --- Holds the different types of cookies we can make. -CREATE TABLE IF NOT EXISTS cookies ( - cookie_id INTEGER PRIMARY KEY, - cookie_name VARCHAR(50) NOT NULL UNIQUE -); - -- Our known customers, may need more fields CREATE TABLE IF NOT EXISTS customers ( customer_id INTEGER PRIMARY KEY, @@ -29,41 +23,35 @@ CREATE TABLE IF NOT EXISTS customers ( CREATE TABLE IF NOT EXISTS orders ( order_id INTEGER PRIMARY KEY, customer_id INT NOT NULL, - cookie_id INT NOT NULL, order_date DATE NOT NULL DEFAULT CURRENT_DATE CHECK (order_date >= CURRENT_DATE), - FOREIGN KEY (customer_id) REFERENCES customers(customer_id), - FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) + expected_delivery_date DATE NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) +); + +CREATE TABLE IF NOT EXISTS order_spec ( + nbr_pallets INTEGER NOT NULL, + order_id INTEGER NOT NULL, + cookie_id INTEGER NOT NULL, + FOREIGN KEY (order_id) REFERENCES orders(order_id), + FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id), + PRIMARY KEY (order_id, cookie_id) ); -------------------------------------------- --- Raw materials, ingredients and recipes +-- Cookies, raw_materials and recipes -------------------------------------------- -- Notes: the unit type can be defined in terms -- of volume or weight instead. Here we choose -- to use static si-prefixes in relevant tables. --- What types of ingredients do we handle. --- Not currently used, but kept as an example. -CREATE TABLE IF NOT EXISTS ingredients ( - ingredient_id INTEGER PRIMARY KEY, - ingredient_name VARCHAR(50) NOT NULL UNIQUE, - preferred_unit VARCHAR(50) NOT NULL CHECK (preferred_unit IN ('g', 'ml')) +-- Holds the different types of cookies we can make. +CREATE TABLE IF NOT EXISTS cookies ( + cookie_id INTEGER PRIMARY KEY, + cookie_name VARCHAR(50) NOT NULL UNIQUE ); --- What ingredients are in what cookies? --- Glues together the cookies and ingredients, a 'recipe'. -CREATE TABLE IF NOT EXISTS recipe_contents ( - cookie_id INT NOT NULL, - ingredient_id INT NOT NULL, - quantity INT NOT NULL, - unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')), - PRIMARY KEY (cookie_id, ingredient_id), - FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id), - FOREIGN KEY (ingredient_id) REFERENCES ingredients(ingredient_id) -); - --- Describes ingredients and stock. --- This should reference the ingredients table, but we'll keep it simple for now. +-- What types of raw_materials do we handle. +-- raw_materials quantity tells us amount in stock CREATE TABLE IF NOT EXISTS raw_materials ( ingredient_id INTEGER PRIMARY KEY, ingredient_name VARCHAR(50) NOT NULL UNIQUE, @@ -71,7 +59,19 @@ CREATE TABLE IF NOT EXISTS raw_materials ( unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')) ); --- When did we get the ingredients? +-- What raw_materials are in what cookies? +-- Glues together the cookies and raw_materials, a 'recipe'. +CREATE TABLE IF NOT EXISTS recipe_contents ( + cookie_id INT NOT NULL, + ingredient_id INT NOT NULL, + quantity INT NOT NULL, + unit VARCHAR(50) NOT NULL CHECK (unit IN ('g', 'ml')), + PRIMARY KEY (cookie_id, ingredient_id), + FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id), + FOREIGN KEY (ingredient_id) REFERENCES raw_materials(ingredient_id) +); + +-- When did we get the raw_materials? CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( delivery_id INTEGER PRIMARY KEY, ingredient_id INT NOT NULL, @@ -90,11 +90,9 @@ CREATE TABLE IF NOT EXISTS raw_materials_deliveries ( CREATE TABLE IF NOT EXISTS pallets ( pallet_id INTEGER PRIMARY KEY, cookie_id INT NOT NULL, - order_id INT, -- This should be not null status VARCHAR(50) NOT NULL CHECK (status IN ('freezer', 'delivered', 'blocked')), - production_date DATE NOT NULL, + production_date DATE NOT NULL DEFAULT NOW, FOREIGN KEY (cookie_id) REFERENCES cookies(cookie_id) - FOREIGN KEY (order_id) REFERENCES orders(order_id) ); -- Connects pallets to orders @@ -116,7 +114,6 @@ CREATE VIEW IF NOT EXISTS pallets_view AS SELECT pallets.pallet_id, cookie_name, - pallets.order_id, status, production_date, delivery_date From 625b5874b15322337a1837eced3f1da93bd4c86c Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 13:28:08 +0200 Subject: [PATCH 34/38] Zip target --- .gitignore | 1 + makefile | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 151a90e..0dbaa06 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ krusty.sqlite3 *.sqlite3 *.db *.tar.gz +*.zip *.minisig *.jpg *.pdf diff --git a/makefile b/makefile index a6ea063..90fb2e4 100644 --- a/makefile +++ b/makefile @@ -28,6 +28,9 @@ release: scp krusty-imbus_$(GITHASH).tar.gz server:/public/krusty/krusty-imbus_$(GITHASH).tar.gz scp krusty-imbus_$(GITHASH).tar.gz.minisig server:/public/krusty/krusty-imbus_$(GITHASH).tar.gz.minisig +zip: + git archive --format=zip --output=krusty-imbus_$(GITHASH).zip HEAD + # Generate ERD. Requires eralchemy2 (pip install eralchemy2) erd: eralchemy2 -i sqlite:///app/krusty.db -o erd.jpg From de0d667d7962bda71088de07a1793f6b7c6e0ad1 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 13:29:06 +0200 Subject: [PATCH 35/38] ... --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index 90fb2e4..fffb908 100644 --- a/makefile +++ b/makefile @@ -8,7 +8,7 @@ run: clean: ./gradlew clean - rm -f *.tar.gz *.tar.gz.minisig + rm -f *.tar.gz *.tar.gz.minisig *.zip rm -f app/krusty.db test: From 50f86a1682dd42e3579eb546fbcf73933abd30ff Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 13:30:43 +0200 Subject: [PATCH 36/38] Zip target final --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index fffb908..8d19e50 100644 --- a/makefile +++ b/makefile @@ -29,7 +29,7 @@ release: scp krusty-imbus_$(GITHASH).tar.gz.minisig server:/public/krusty/krusty-imbus_$(GITHASH).tar.gz.minisig zip: - git archive --format=zip --output=krusty-imbus_$(GITHASH).zip HEAD + git archive --format=zip --prefix krusty11/ --output=krusty11-$(GITHASH).zip HEAD # Generate ERD. Requires eralchemy2 (pip install eralchemy2) erd: From eb3b594aaa218e52bd085c53ded5e7dfe77406a5 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 13:32:20 +0200 Subject: [PATCH 37/38] ERD target proper dependencies in make --- makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makefile b/makefile index 8d19e50..30db587 100644 --- a/makefile +++ b/makefile @@ -8,7 +8,7 @@ run: clean: ./gradlew clean - rm -f *.tar.gz *.tar.gz.minisig *.zip + rm -f *.tar.gz *.tar.gz.minisig *.zip *.jpg rm -f app/krusty.db test: @@ -32,7 +32,7 @@ zip: git archive --format=zip --prefix krusty11/ --output=krusty11-$(GITHASH).zip HEAD # Generate ERD. Requires eralchemy2 (pip install eralchemy2) -erd: +erd: migrate eralchemy2 -i sqlite:///app/krusty.db -o erd.jpg .PHONY: run clean test build dbdump migrate release erd From d69a9dca2d4076493ae972cfa3c5e612ab4e9362 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 13:59:43 +0200 Subject: [PATCH 38/38] Zip target and build instructions --- README.md | 18 ++++++++++++++++++ makefile | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f1ac04..017c7d0 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,26 @@ > Krusty Kookies is a bakery which specializes in cookies, and they need a database to keep track of their production and deliveries. +## Building and testing + +This project uses sqlite3 as a database. Migrations happens automatically on launch. +Migrations drop all tables and recreate them, so all data is lost on restart. +By default jdbc connects sqlite to an in-memory database, so all data is lost on restart anyway. + +```bash +./gradlew build +./gradlew test +``` + +The gradle environment is bumped to a recent version, and it is configured with kotlin and junit5. +The syntax for junit4 was ported to junit5. +**Most** of the pre-configured deps in the handout contained CVEs of varying severity, so they were updated to newer versions. +No tests were changed, some helper classes were implemented. + ## Base tables +**This description is no longer consistent with the current state of the project.** + Unsuprisingly, we will need a cookie table. ```sql diff --git a/makefile b/makefile index 30db587..1459c69 100644 --- a/makefile +++ b/makefile @@ -29,7 +29,8 @@ release: scp krusty-imbus_$(GITHASH).tar.gz.minisig server:/public/krusty/krusty-imbus_$(GITHASH).tar.gz.minisig zip: - git archive --format=zip --prefix krusty11/ --output=krusty11-$(GITHASH).zip HEAD + git archive --format=zip --prefix Rest11/ --output=Rest11.zip HEAD + 7za a -tzip CourseProject11.zip ./app/Migrations/*.sql # Generate ERD. Requires eralchemy2 (pip install eralchemy2) erd: migrate