Compare commits

..

12 commits

Author SHA1 Message Date
Imbus
3530b4d140 Ignore 2024-05-03 08:18:53 +02:00
Imbus
43958cffd4 Some default recipes 2024-05-03 08:17:00 +02:00
Imbus
c27f07d06d Makefile target for manual migration to help with debugging 2024-05-03 08:16:54 +02:00
Imbus
0b9c183a2d Fixing migration method 2024-05-03 08:16:38 +02:00
Imbus
0416a1d3da Correcting sql, work on tables 2024-05-03 08:16:21 +02:00
Imbus
83e82c93aa Update script location in main 2024-05-03 05:36:07 +02:00
Imbus
87ad067bbf Rename migration scripts 2024-05-03 05:35:13 +02:00
Imbus
570366c8e7 Reworking tables 2024-05-03 05:30:52 +02:00
Imbus
5988b459b4 Recipe and ingredient helper classes 2024-05-03 05:30:29 +02:00
Imbus
a1ac8a366b Select helper, unsafe but functional 2024-05-03 05:29:54 +02:00
Imbus
87ba5bb679 Snake case table names 2024-05-03 01:59:07 +02:00
Imbus
e6560653d9 Testing dependencies 2024-05-03 01:18:43 +02:00
12 changed files with 270 additions and 116 deletions

5
.gitignore vendored
View file

@ -23,4 +23,7 @@ gradle-app.setting
__MACOSX
.DS_Store
.vscode
krusty.sqlite3
krusty.sqlite3
*.sqlite3
*.db

View file

@ -1,88 +0,0 @@
CREATE TABLE IF NOT EXISTS Customers (
CustomerID int PRIMARY KEY,
Name varchar(100),
Address varchar(255)
);
CREATE TABLE IF NOT EXISTS Products (
ProductID int PRIMARY KEY,
Name varchar(100)
);
CREATE TABLE IF NOT EXISTS Recipes (
RecipeName varchat(100),
RecipeYear Year,
ingrediences int,
ProductID int,
PRIMARY KEY (RecipeName, RecipeYear),
FOREIGN KEY (ingrediences) REFERENCES ingredience(IngredienceID),
FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
);
CREATE TABLE IF NOT EXISTS ingredience (
IngredienceID int PRIMARY KEY,
RawMaterialName varchar(100),
amount int,
unit varchar(50),
FOREIGN KEY (RawMaterialName) REFERENCES RawMaterials(RawMaterialName)
);
CREATE TABLE IF NOT EXISTS RawMaterials (
RawMaterialName varchar(100) PRIMARY KEY,
Quantity int,
LastDeliveryDateTime datetime
);
CREATE TABLE IF NOT EXISTS PalletsProduced (
PalletID int PRIMARY KEY,
ProductID int,
ProductionDateTime datetime,
FOREIGN KEY (ProductID) REFERENCES Products (ProductID)
);
CREATE TABLE IF NOT EXISTS PalletsDelivered (
DeliveredID int PRIMARY KEY,
PalletID int,
DeliveryDateTime datetime,
FOREIGN KEY (PalletID) REFERENCES PalletsProduced (PalletID),
FOREIGN KEY (DeliveredID) REFERENCES Truck (Pallet)
);
CREATE TABLE IF NOT EXISTS Truck (
truckId int PRIMARY KEY,
capacity int,
Pallet int
);
CREATE TABLE IF NOT EXISTS loadingBill (
LoadingbillID int PRIMARY KEY,
adress varchar(100),
customer varchar(100),
truckID int,
FOREIGN KEY (truckID) REFERENCES Truck (truckId)
);
CREATE TABLE IF NOT EXISTS Orders (
OrderID int PRIMARY KEY,
CustomerID int,
ProductID int,
Quantity int,
OrderDateTime datetime,
FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID),
FOREIGN KEY (ProductID) REFERENCES Products (ProductID)
);
CREATE TABLE IF NOT EXISTS BlockedProducts (
BlockedProductID int PRIMARY KEY,
ProductID int,
BlockedDateTime datetime,
FOREIGN KEY (ProductID) REFERENCES Products (ProductID)
);
CREATE TABLE IF NOT EXISTS PalletTraceability (
TraceID int PRIMARY KEY,
location varchar(100),
locationdate datetime,
PalletID int,
FOREIGN KEY (PalletID) REFERENCES PalletsProduced (PalletID)
);

View file

@ -1,4 +0,0 @@
-- Inserts here
INSERT INTO Customers (CustomerID, Name, Address) VALUES
(1, 'John Doe', '123 Main St'),
(2, 'Jane Smith', '456 Elm St');

View file

@ -0,0 +1,83 @@
--------------------------------------------
-- Recipe/Cookie related tables
--------------------------------------------
-- 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)
);
-- 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)
);
--------------------------------------------
-- 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) -- 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)
);
-- 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)
);
--------------------------------------------
-- Pallet related tables
--------------------------------------------
-- 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)
);
-- 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)
);

View file

@ -0,0 +1,22 @@
-- Inserts here
INSERT OR IGNORE INTO
customers (customer_id, customer_name, customer_address)
VALUES
(1, 'Bjudkakor AB', 'Ystad'),
(2, 'Finkakor AB', 'Helsingborg'),
(3, 'Gästkakor AB', 'Hässleholm'),
(4, 'Kaffebröd AB', 'Landskrona'),
(5, 'Kalaskakor AB', 'Trelleborg'),
(6, 'Partykakor AB', 'Kristianstad'),
(7, 'Skånekakor AB', 'Perstorp'),
(8, 'Småbröd AB', 'Malmö');
INSERT INTO
recipes (recipe_name)
VALUES
('Nut ring'),
('Nut cookie'),
('Amneris'),
('Tango'),
('Almond delight'),
('Berliner');

View file

@ -18,8 +18,8 @@ repositories {
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
// testImplementation("org.skyscreamer:jsonassert:1.5.0") // For JSON assertions in tests.
// testImplementation("com.mashape.unirest:unirest-java:1.4.9") // For HTTP requests in tests.
testImplementation("org.skyscreamer:jsonassert:1.5.0") // For JSON assertions in tests.
testImplementation("com.mashape.unirest:unirest-java:1.4.9") // For HTTP requests in tests.
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.2")
// implementation("com.google.guava:guava:33.1.0-jre") // Currently not used.

View file

@ -8,21 +8,29 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.io.BufferedReader;
// Likely dependencies for general operations
import java.io.IOException;
import java.io.FileReader;
import java.sql.ResultSet;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.nio.file.Files;
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:<filename>.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::memory:";
private static final String jdbcString = "jdbc:sqlite:krusty.db";
private Connection conn = null;
public String getCustomers(Request req, Response res) {
return "{}";
String result = selectQuery("customers", "customers", "customer_name", "customer_address");
result = result.replaceAll("customer_name", "name");
result = result.replaceAll("customer_address", "address");
return result;
}
public String getRawMaterials(Request req, Response res) {
@ -30,7 +38,8 @@ public class Database {
}
public String getCookies(Request req, Response res) {
return "{\"cookies\":[]}";
String result = selectQuery("recipes", "cookies", "recipe_name");
return result;
}
public String getRecipes(Request req, Response res) {
@ -60,31 +69,65 @@ public class Database {
}
}
/**
* Selects columns from a table and returns the result as a JSON string.
* Does _absolutely no_ query sanitization, so be careful with user input.
*/
private String selectQuery(String table, String jsonName, String... columns) {
String jsonResult = "{}"; // Valid json to return if fail
try {
Statement stmt = this.conn.createStatement();
StringBuilder query = new StringBuilder("SELECT ");
StringJoiner args = new StringJoiner(", ");
for (String column : columns) {
args.add(column);
}
query.append(args.toString());
query.append(" FROM " + table + ";");
/* Sanitization is for cowards */
ResultSet result = stmt.executeQuery(query.toString());
jsonResult = Jsonizer.toJson(result, jsonName);
} catch (SQLException e) {
System.out.printf("Error executing query: \n%s", e);
}
return jsonResult;
}
// The script location is relative to the gradle
// build script ("build.gradle.kts", in this case).
/** Reads an sql script into the database */
public void migrateScript(String filename) throws IOException, SQLException {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
StringBuilder scriptBuilder = new StringBuilder();
String line;
try (Stream<String> lines = Files.lines(Paths.get(filename))) {
// Read the script file line by line
while ((line = reader.readLine()) != null) {
scriptBuilder.append(line).append("\n");
}
String script = scriptBuilder.toString().trim();
// Combine into one big string, with all comments and empty lines removed.
String[] statements = lines.filter(line -> !line.startsWith("--") && !line.isBlank())
.map(line -> line.replaceAll("--.*", "").replaceAll("\\s+", " ").trim())
.collect(Collectors.joining("\n")).split(";");
// Execute the script
try (Statement statement = conn.createStatement()) {
statement.execute(script);
for (String query : statements) {
try (Statement statement = conn.createStatement()) {
statement.execute(query);
statement.close();
} catch (SQLException e) {
System.err.println("Error executing script: " + e.getMessage());
throw e;
}
}
System.out.println(String.format("Executed script %s", filename));
} catch (IOException e) {
System.err.println("Error reading script file: " + e.getMessage());
throw e;
} catch (SQLException e) {
System.err.println("Error executing script: " + e.getMessage());
String prepend = String.format("Error executing script: %s", filename);
System.err.println(prepend + e.getMessage());
throw e;
}
}
}
}

View file

@ -0,0 +1,57 @@
package krusty;
import java.util.Arrays;
import java.util.List;
public class DefaultRecipes {
public static List<Recipe> recipes = Arrays.asList(
new Recipe("Nut ring",
new Ingredient[] {
new Ingredient("Flour", 450, "g"),
new Ingredient("Butter", 450, "g"),
new Ingredient("Icing sugar", 190, "g"),
new Ingredient("Roasted, chopped nuts", 225, "g")
}),
new Recipe("Nut cookie",
new Ingredient[] {
new Ingredient("Fine-ground nuts", 750, "g"),
new Ingredient("Ground, roasted nuts", 625, "g"),
new Ingredient("Bread crumbs", 125, "g"),
new Ingredient("Sugar", 375, "g"),
new Ingredient("Egg Whites", 350, "ml"),
new Ingredient("Chocolate", 50, "g")
}),
new Recipe("Amneris",
new Ingredient[] {
new Ingredient("Marzipan", 750, "g"),
new Ingredient("Butter", 250, "g"),
new Ingredient("Eggs", 250, "g"),
new Ingredient("Potato starch", 25, "g"),
new Ingredient("Wheat flour", 25, "g")
}),
new Recipe("Tango",
new Ingredient[] {
new Ingredient("Butter", 200, "g"),
new Ingredient("Sugar", 250, "g"),
new Ingredient("Flour", 300, "g"),
new Ingredient("Sodium bicarbonate", 4, "g"),
new Ingredient("Vanilla", 2, "g")
}),
new Recipe("Almond delight",
new Ingredient[] {
new Ingredient("Butter", 400, "g"),
new Ingredient("Sugar", 270, "g"),
new Ingredient("Chopped almonds", 279, "g"),
new Ingredient("Flour", 400, "g"),
new Ingredient("Cinnamon", 10, "g")
}),
new Recipe("Berliner",
new Ingredient[] {
new Ingredient("Flour", 350, "g"),
new Ingredient("Butter", 250, "g"),
new Ingredient("Icing sugar", 100, "g"),
new Ingredient("Eggs", 50, "g"),
new Ingredient("Vanilla sugar", 5, "g"),
new Ingredient("Chocolate", 50, "g")
}));
}

View file

@ -0,0 +1,16 @@
package krusty;
public class Ingredient {
public String name, unit;
public int amount;
public Ingredient(String name, int amount, String unit) {
this.name = name;
this.amount = amount;
this.unit = unit;
}
public String toString() {
return String.format("%s: %d %s", name, amount, unit);
}
}

View file

@ -0,0 +1,15 @@
package krusty;
public class Recipe {
public String name;
public Ingredient ingredients[];
public Recipe(String name, Ingredient[] ingredients) {
this.name = name;
this.ingredients = ingredients;
}
public String toString() {
return name;
}
}

View file

@ -20,8 +20,8 @@ public class ServerMain {
// Here, we can migrate an arbitrary number of SQL scripts.
try {
db.migrateScript("Migrations/0010-tables.sql");
db.migrateScript("Migrations/0020-data.sql");
db.migrateScript("Migrations/create-schema.sql");
db.migrateScript("Migrations/initial-data.sql");
} catch (Exception e) {
throw new IOError(e);
}

View file

@ -10,4 +10,11 @@ clean:
test:
./gradlew test
.PHONY: run clean test build
dbdump:
sqlite3 app/krusty.db .dump
migrate:
sqlite3 app/krusty.db < app/Migrations/create-schema.sql
sqlite3 app/krusty.db < app/Migrations/initial-data.sql
.PHONY: run clean test build