From 35607ce341bc06e551faa6491ca79ffc3416766e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Sun, 5 May 2024 06:47:56 +0200 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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