diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..bf2c501 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f88c0a7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/database.sql b/database.sql index 2169346..ca67e04 100644 --- a/database.sql +++ b/database.sql @@ -1,141 +1,96 @@ -- Restaurant Database Management System -- --- Instructions: --- 1. Create all required tables. --- 2. Design appropriate PRIMARY KEY and FOREIGN KEY relationships. --- 3. Add suitable constraints based on the requirements. --- 4. Insert initial mock data. --- 5. Insert at least 3 menu items. --- 6. The script should be executable from start to finish without errors. +-- This script creates the whole schema from scratch and inserts some +-- initial data. It can be run from start to finish without errors. +-- +-- Run with: psql -U postgres -d restaurant_db -f database.sql +-- Drop old tables first so the script is re-runnable. +-- Order matters because of the foreign keys (drop children before parents). +DROP TABLE IF EXISTS order_details CASCADE; +DROP TABLE IF EXISTS orders CASCADE; +DROP TABLE IF EXISTS menu_items CASCADE; +DROP TABLE IF EXISTS users CASCADE; + -- ======================================================= -- USER TABLE -- ======================================================= --- -- Represents customers using the system. --- --- Required information: --- - Unique identifier --- - Username --- - Password --- - Email (optional) --- --- Requirements: --- - Each user must have a unique identifier. --- - Usernames must be unique. --- - Username and password are required. --- - Passwords should not be stored in plain text. --- --- CREATE TABLE ... - +-- Passwords are stored as a hash (never plain text) by the Java app. +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, -- SHA-256 hex hash + email VARCHAR(255) -- optional +); -- ======================================================= -- MENU ITEM TABLE -- ======================================================= --- -- Represents available food and drink items. --- --- Required information: --- - Unique identifier --- - Name --- - Description (optional) --- - Price --- - Category (optional) --- --- Requirements: --- - Each menu item must have a unique identifier. --- - Name is required. --- - Price must always be positive. --- --- CREATE TABLE ... - +CREATE TABLE menu_items ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, -- optional + price NUMERIC(10, 2) NOT NULL CHECK (price > 0), + category VARCHAR(50) -- optional +); -- ======================================================= --- ORDER TABLE +-- ORDER TABLE ("order" is a reserved word, so we use "orders") -- ======================================================= --- --- Represents orders placed by customers. --- --- Required information: --- - Unique identifier --- - Reference to customer --- - Creation date and time --- - Total price --- --- Requirements: --- - Each order must belong to exactly one user. --- - A user can have multiple orders. --- - The relationship between User and Order must be implemented. --- --- Note: --- Avoid using reserved SQL keywords as table names. --- Consider using a name such as "orders" or "customer_orders". --- --- CREATE TABLE ... - +-- Each order belongs to exactly one user (One-to-Many: user -> orders). +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL REFERENCES users(id), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + total_price NUMERIC(10, 2) NOT NULL DEFAULT 0 CHECK (total_price >= 0) +); -- ======================================================= -- ORDER DETAIL TABLE -- ======================================================= --- --- Represents items inside an order. --- --- Required information: --- - Unique identifier --- - Reference to an order --- - Reference to a menu item --- - Quantity --- - Item price at purchase time --- --- Requirements: --- - Each detail record must belong to one order. --- - Each detail record must reference one menu item. --- - Quantity must always be greater than zero. --- - Store the item's price at the moment of purchase. --- --- CREATE TABLE ... - +-- One line inside an order (One-to-Many: order -> order_details). +-- "price" is the item price at the moment of purchase, so history stays +-- correct even if the menu price changes later. +CREATE TABLE order_details ( + id SERIAL PRIMARY KEY, + order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, + menu_item_id INT NOT NULL REFERENCES menu_items(id), + quantity INT NOT NULL CHECK (quantity > 0), + price NUMERIC(10, 2) NOT NULL CHECK (price > 0) +); -- ======================================================= --- INITIAL MENU DATA +-- INITIAL MENU DATA (at least 3 items) -- ======================================================= --- --- Insert at least 3 food or drink items. --- --- Example categories: --- - Pizza --- - Burger --- - Pasta --- - Drink --- --- INSERT INTO ... - +INSERT INTO menu_items (name, description, price, category) VALUES + ('Margherita Pizza', 'Classic pizza with tomato, mozzarella and basil', 10.00, 'Pizza'), + ('Cheeseburger', 'Beef patty with cheddar, lettuce and tomato', 8.00, 'Burger'), + ('Pasta Bolognese', 'Spaghetti with rich beef and tomato sauce', 12.00, 'Pasta'), + ('Caesar Salad', 'Romaine, croutons, parmesan and Caesar dressing', 6.50, 'Salad'), + ('Coca-Cola', 'Chilled 330ml can', 2.50, 'Drink'); -- ======================================================= -- OPTIONAL TEST DATA -- ======================================================= --- --- You may insert sample users and orders for testing. --- This section is optional. --- --- INSERT INTO ... - +-- Sample user. The password below is the SHA-256 hash of the text "1234" +-- so you can log in with username "admin" / password "1234" for testing. +INSERT INTO users (username, password, email) VALUES + ('admin', '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4', 'admin@pizzeria.com'); -- ======================================================= --- VERIFICATION QUERIES +-- VERIFICATION QUERIES (optional, uncomment to check) -- ======================================================= --- --- Uncomment these queries to verify your database. --- --- SELECT * FROM ...; --- SELECT * FROM ...; --- SELECT * FROM ...; --- SELECT * FROM ...; \ No newline at end of file +-- SELECT * FROM users; +-- SELECT * FROM menu_items; +-- SELECT * FROM orders; +-- SELECT * FROM order_details; \ No newline at end of file diff --git a/src/main/java/dev/dao/MenuItemDao.java b/src/main/java/dev/dao/MenuItemDao.java index 3bbebe9..6b54407 100644 --- a/src/main/java/dev/dao/MenuItemDao.java +++ b/src/main/java/dev/dao/MenuItemDao.java @@ -1,25 +1,69 @@ package dev.dao; +import dev.database.DatabaseConnection; import dev.model.MenuItem; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; public class MenuItemDao { public List findAll() { - // TODO: - // Retrieve all menu items + List items = new ArrayList<>(); - return null; + String sql = "SELECT id, name, description, price, category FROM menu_items ORDER BY id"; + + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + while (rs.next()) { + items.add(mapRow(rs)); + } + + } catch (SQLException e) { + System.out.println("Error while loading menu: " + e.getMessage()); + } + + return items; } public MenuItem findById(int id) { - // TODO: - // Find menu item by id + String sql = "SELECT id, name, description, price, category FROM menu_items WHERE id = ?"; + + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setInt(1, id); + + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return mapRow(rs); + } + } + + } catch (SQLException e) { + System.out.println("Error while finding menu item: " + e.getMessage()); + } return null; } -} \ No newline at end of file + /** Turns the current row of a ResultSet into a MenuItem object. */ + private MenuItem mapRow(ResultSet rs) throws SQLException { + return new MenuItem( + rs.getInt("id"), + rs.getString("name"), + rs.getString("description"), + rs.getDouble("price"), + rs.getString("category") + ); + } + +} diff --git a/src/main/java/dev/dao/OrderDao.java b/src/main/java/dev/dao/OrderDao.java index 00999a2..e9b101a 100644 --- a/src/main/java/dev/dao/OrderDao.java +++ b/src/main/java/dev/dao/OrderDao.java @@ -1,25 +1,80 @@ package dev.dao; +import dev.database.DatabaseConnection; import dev.model.Order; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.ArrayList; import java.util.List; public class OrderDao { + /** + * Inserts an order and returns the id the database generated for it. + * Returns -1 if something went wrong. We let the DB fill in created_at + * with its DEFAULT, and only send user_id and total_price. + */ public int save(Order order) { - // TODO: - // Insert order and return generated id + String sql = "INSERT INTO orders (user_id, total_price) VALUES (?, ?)"; + + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = + conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + stmt.setInt(1, order.getUserId()); + stmt.setDouble(2, order.getTotalPrice()); + + stmt.executeUpdate(); + + // Read back the auto-generated id (SERIAL primary key). + try (ResultSet keys = stmt.getGeneratedKeys()) { + if (keys.next()) { + return keys.getInt(1); + } + } + + } catch (SQLException e) { + System.out.println("Error while saving order: " + e.getMessage()); + } return -1; } public List findByUserId(int userId) { - // TODO: - // Retrieve all orders of a user + List orders = new ArrayList<>(); - return null; + String sql = "SELECT id, user_id, created_at, total_price " + + "FROM orders WHERE user_id = ? ORDER BY created_at DESC"; + + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setInt(1, userId); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + Timestamp ts = rs.getTimestamp("created_at"); + orders.add(new Order( + rs.getInt("id"), + rs.getInt("user_id"), + ts.toLocalDateTime(), + rs.getDouble("total_price") + )); + } + } + + } catch (SQLException e) { + System.out.println("Error while loading orders: " + e.getMessage()); + } + + return orders; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/dao/OrderDetailDao.java b/src/main/java/dev/dao/OrderDetailDao.java index ed42f56..09af41a 100644 --- a/src/main/java/dev/dao/OrderDetailDao.java +++ b/src/main/java/dev/dao/OrderDetailDao.java @@ -1,24 +1,66 @@ package dev.dao; +import dev.database.DatabaseConnection; import dev.model.OrderDetail; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; public class OrderDetailDao { public void save(OrderDetail detail) { - // TODO: - // Insert order detail + String sql = "INSERT INTO order_details (order_id, menu_item_id, quantity, price) " + + "VALUES (?, ?, ?, ?)"; + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setInt(1, detail.getOrderId()); + stmt.setInt(2, detail.getMenuItemId()); + stmt.setInt(3, detail.getQuantity()); + stmt.setDouble(4, detail.getPrice()); + + stmt.executeUpdate(); + + } catch (SQLException e) { + System.out.println("Error while saving order detail: " + e.getMessage()); + } } public List findByOrderId(int orderId) { - // TODO: - // Retrieve order details + List details = new ArrayList<>(); - return null; + String sql = "SELECT id, order_id, menu_item_id, quantity, price " + + "FROM order_details WHERE order_id = ? ORDER BY id"; + + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setInt(1, orderId); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + details.add(new OrderDetail( + rs.getInt("id"), + rs.getInt("order_id"), + rs.getInt("menu_item_id"), + rs.getInt("quantity"), + rs.getDouble("price") + )); + } + } + + } catch (SQLException e) { + System.out.println("Error while loading order details: " + e.getMessage()); + } + + return details; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/dao/UserDao.java b/src/main/java/dev/dao/UserDao.java index af08106..c1249da 100644 --- a/src/main/java/dev/dao/UserDao.java +++ b/src/main/java/dev/dao/UserDao.java @@ -1,23 +1,68 @@ package dev.dao; +import dev.database.DatabaseConnection; import dev.model.User; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + public class UserDao { + /** + * Inserts a new user. The password passed in here is expected to already + * be hashed by the service layer. Returns true if the insert worked. + */ public boolean save(User user) { - // TODO: - // Insert user into database + String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)"; - return false; + // try-with-resources closes the connection and statement automatically. + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, user.getUsername()); + stmt.setString(2, user.getPassword()); + stmt.setString(3, user.getEmail()); + + int rows = stmt.executeUpdate(); + return rows > 0; + + } catch (SQLException e) { + System.out.println("Error while saving user: " + e.getMessage()); + return false; + } } + /** + * Looks up a user by username. Returns null if nobody matches. + */ public User findByUsername(String username) { - // TODO: - // Find a user by username + String sql = "SELECT id, username, password, email FROM users WHERE username = ?"; + + try (Connection conn = DatabaseConnection.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, username); + + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return new User( + rs.getInt("id"), + rs.getString("username"), + rs.getString("password"), + rs.getString("email") + ); + } + } + + } catch (SQLException e) { + System.out.println("Error while finding user: " + e.getMessage()); + } return null; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/database/DatabaseConnection.java b/src/main/java/dev/database/DatabaseConnection.java index da45535..15822fd 100644 --- a/src/main/java/dev/database/DatabaseConnection.java +++ b/src/main/java/dev/database/DatabaseConnection.java @@ -1,8 +1,13 @@ package dev.database; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.SQLException; +/** + * Small helper that hands out PostgreSQL connections. + * Every DAO calls getConnection() when it needs to talk to the database. + */ public class DatabaseConnection { private static final String URL = "jdbc:postgresql://localhost:5432/restaurant_db"; // DB Server @@ -18,10 +23,10 @@ public class DatabaseConnection { public static Connection getConnection() throws SQLException { - // TODO: - // Return a valid PostgreSQL connection - - return null; + // DriverManager opens a fresh TCP connection to PostgreSQL using the + // JDBC URL and credentials above. The postgresql driver on the + // classpath registers itself automatically, so no Class.forName needed. + return DriverManager.getConnection(URL, USER, PASSWORD); } -} \ No newline at end of file +} diff --git a/src/main/java/dev/model/MenuItem.java b/src/main/java/dev/model/MenuItem.java index 265ecc1..ef7fc33 100644 --- a/src/main/java/dev/model/MenuItem.java +++ b/src/main/java/dev/model/MenuItem.java @@ -12,4 +12,55 @@ public class MenuItem { private String category; -} \ No newline at end of file + public MenuItem() { + } + + public MenuItem(int id, String name, String description, double price, String category) { + this.id = id; + this.name = name; + this.description = description; + this.price = price; + this.category = category; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + +} diff --git a/src/main/java/dev/model/Order.java b/src/main/java/dev/model/Order.java index 32049cb..2e6b536 100644 --- a/src/main/java/dev/model/Order.java +++ b/src/main/java/dev/model/Order.java @@ -12,4 +12,46 @@ public class Order { private double totalPrice; -} \ No newline at end of file + public Order() { + } + + public Order(int id, int userId, LocalDateTime createdAt, double totalPrice) { + this.id = id; + this.userId = userId; + this.createdAt = createdAt; + this.totalPrice = totalPrice; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public double getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(double totalPrice) { + this.totalPrice = totalPrice; + } + +} diff --git a/src/main/java/dev/model/OrderDetail.java b/src/main/java/dev/model/OrderDetail.java index b5b8bbe..b5c2ee4 100644 --- a/src/main/java/dev/model/OrderDetail.java +++ b/src/main/java/dev/model/OrderDetail.java @@ -12,4 +12,60 @@ public class OrderDetail { private double price; -} \ No newline at end of file + public OrderDetail() { + } + + public OrderDetail(int id, int orderId, int menuItemId, int quantity, double price) { + this.id = id; + this.orderId = orderId; + this.menuItemId = menuItemId; + this.quantity = quantity; + this.price = price; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getOrderId() { + return orderId; + } + + public void setOrderId(int orderId) { + this.orderId = orderId; + } + + public int getMenuItemId() { + return menuItemId; + } + + public void setMenuItemId(int menuItemId) { + this.menuItemId = menuItemId; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + /** Convenience: price for this line = unit price * quantity. */ + public double getSubtotal() { + return price * quantity; + } + +} diff --git a/src/main/java/dev/model/User.java b/src/main/java/dev/model/User.java index 0ab60c6..666547a 100644 --- a/src/main/java/dev/model/User.java +++ b/src/main/java/dev/model/User.java @@ -10,4 +10,46 @@ public class User { private String email; + public User() { + } + + public User(int id, String username, String password, String email) { + this.id = id; + this.username = username; + this.password = password; + this.email = email; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + } \ No newline at end of file diff --git a/src/main/java/dev/service/AuthService.java b/src/main/java/dev/service/AuthService.java index bee866a..6815e93 100644 --- a/src/main/java/dev/service/AuthService.java +++ b/src/main/java/dev/service/AuthService.java @@ -1,23 +1,79 @@ package dev.service; +import dev.dao.UserDao; import dev.model.User; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + public class AuthService { + private final UserDao userDao = new UserDao(); + + /** + * Registers a new user. Checks the username is free and that the fields + * are not empty, then stores the password as a SHA-256 hash. + * Returns true only if the account was actually created. + */ public boolean register(String username, String password, String email) { - // TODO: - // Validate and register user + if (username == null || username.isBlank() + || password == null || password.isBlank()) { + System.out.println("Username and password are required."); + return false; + } - return false; + // Username must be unique. + if (userDao.findByUsername(username) != null) { + System.out.println("That username is already taken."); + return false; + } + + User user = new User(); + user.setUsername(username); + user.setPassword(hash(password)); // never store plain text + user.setEmail(email == null || email.isBlank() ? null : email); + + return userDao.save(user); } + /** + * Checks the given credentials against the database. + * Returns the User on success, or null if the username does not exist + * or the password is wrong. + */ public User login(String username, String password) { - // TODO: - // Authenticate user + User user = userDao.findByUsername(username); + if (user == null) { + return null; // no such username + } - return null; + // Hash the entered password and compare with the stored hash. + if (user.getPassword().equals(hash(password))) { + return user; + } + + return null; // wrong password } -} \ No newline at end of file + /** Hashes text with SHA-256 and returns it as a lowercase hex string. */ + private String hash(String text) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] bytes = digest.digest(text.getBytes(StandardCharsets.UTF_8)); + + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + + } catch (NoSuchAlgorithmException e) { + // SHA-256 is always available, so this should never happen. + throw new RuntimeException("SHA-256 not available", e); + } + } + +} diff --git a/src/main/java/dev/service/MenuService.java b/src/main/java/dev/service/MenuService.java index 6dbf4da..37df145 100644 --- a/src/main/java/dev/service/MenuService.java +++ b/src/main/java/dev/service/MenuService.java @@ -1,12 +1,40 @@ package dev.service; +import dev.dao.MenuItemDao; +import dev.model.MenuItem; + +import java.util.List; + public class MenuService { - public void showMenu() { - - // TODO: - // Display menu items + private final MenuItemDao menuItemDao = new MenuItemDao(); + /** Loads every menu item so other parts of the app can use them. */ + public List getMenu() { + return menuItemDao.findAll(); } -} \ No newline at end of file + /** Prints the whole menu to the console in a simple table. */ + public void showMenu() { + + List items = menuItemDao.findAll(); + + if (items.isEmpty()) { + System.out.println("The menu is empty."); + return; + } + + System.out.println("---------------------------------------------"); + System.out.printf("%-4s %-20s %-10s %-10s%n", "ID", "Name", "Price", "Category"); + System.out.println("---------------------------------------------"); + + for (MenuItem item : items) { + String category = item.getCategory() == null ? "-" : item.getCategory(); + System.out.printf("%-4d %-20s $%-9.2f %-10s%n", + item.getId(), item.getName(), item.getPrice(), category); + } + + System.out.println("---------------------------------------------"); + } + +} diff --git a/src/main/java/dev/service/OrderService.java b/src/main/java/dev/service/OrderService.java index 14708f8..7416d53 100644 --- a/src/main/java/dev/service/OrderService.java +++ b/src/main/java/dev/service/OrderService.java @@ -1,26 +1,168 @@ package dev.service; +import dev.dao.MenuItemDao; +import dev.dao.OrderDao; +import dev.dao.OrderDetailDao; +import dev.model.MenuItem; +import dev.model.Order; +import dev.model.OrderDetail; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + public class OrderService { + private final MenuItemDao menuItemDao = new MenuItemDao(); + private final OrderDao orderDao = new OrderDao(); + private final OrderDetailDao orderDetailDao = new OrderDetailDao(); + + private final Scanner scanner; + + /** The Scanner is shared with the console UI so input stays consistent. */ + public OrderService(Scanner scanner) { + this.scanner = scanner; + } + + /** + * Lets the logged-in user build an order item by item, then stores the + * Order and its OrderDetail rows in the database and prints a receipt. + */ public void placeOrder(int userId) { - // TODO: - // Create order + List menu = menuItemDao.findAll(); + if (menu.isEmpty()) { + System.out.println("Sorry, there is nothing on the menu right now."); + return; + } + System.out.println("\nAvailable Items:"); + for (MenuItem item : menu) { + System.out.printf("%d. %s - $%.2f%n", item.getId(), item.getName(), item.getPrice()); + } + + // The "cart": order lines the user has chosen so far. + List cart = new ArrayList<>(); + + while (true) { + System.out.print("\nEnter the ID of the item to add (or 0 to finish): "); + int itemId = readInt(); + + if (itemId == 0) { + break; + } + + MenuItem item = menuItemDao.findById(itemId); + if (item == null) { + System.out.println("No item with that ID. Try again."); + continue; + } + + System.out.print("Enter quantity: "); + int quantity = readInt(); + if (quantity <= 0) { + System.out.println("Quantity must be at least 1."); + continue; + } + + // Store the current price so the receipt stays correct later. + OrderDetail line = new OrderDetail(); + line.setMenuItemId(item.getId()); + line.setQuantity(quantity); + line.setPrice(item.getPrice()); + cart.add(line); + + System.out.printf("Added %dx %s to your cart.%n", quantity, item.getName()); + } + + if (cart.isEmpty()) { + System.out.println("Your cart is empty, order cancelled."); + return; + } + + // Work out the grand total. + double total = 0; + for (OrderDetail line : cart) { + total += line.getSubtotal(); + } + + // Save the order first so we get its generated id. + Order order = new Order(); + order.setUserId(userId); + order.setTotalPrice(total); + + int orderId = orderDao.save(order); + if (orderId == -1) { + System.out.println("Could not save the order. Please try again."); + return; + } + + // Now save each line with the order id we just got back. + for (OrderDetail line : cart) { + line.setOrderId(orderId); + orderDetailDao.save(line); + } + + printReceipt(orderId); + System.out.println("Order saved successfully!"); } + /** + * Reads an order back from the database and prints a detailed receipt: + * item names, quantities, unit prices, subtotals and the grand total. + */ public void printReceipt(int orderId) { - // TODO: - // Print order receipt + List details = orderDetailDao.findByOrderId(orderId); + if (details.isEmpty()) { + System.out.println("No details found for order #" + orderId); + return; + } + System.out.println("\n[Order Summary / Receipt]"); + System.out.println("---------------------------------------------"); + System.out.printf("%-18s %-6s %-9s %-9s%n", "Item", "Qty", "Unit", "Total"); + System.out.println("---------------------------------------------"); + + double grandTotal = 0; + for (OrderDetail line : details) { + MenuItem item = menuItemDao.findById(line.getMenuItemId()); + String name = (item == null) ? "(removed item)" : item.getName(); + + System.out.printf("%-18s %-6d $%-8.2f $%-8.2f%n", + name, line.getQuantity(), line.getPrice(), line.getSubtotal()); + + grandTotal += line.getSubtotal(); + } + + System.out.println("---------------------------------------------"); + System.out.printf("Final Total: $%.2f%n", grandTotal); } + /** Shows every past order of a user together with what each one cost. */ public void showOrderHistory(int userId) { - // TODO: - // Display user's order history + List orders = orderDao.findByUserId(userId); + if (orders.isEmpty()) { + System.out.println("You have not placed any orders yet."); + return; + } + + System.out.println("\n===== ORDER HISTORY ====="); + for (Order order : orders) { + System.out.printf("Order #%d | %s | Total: $%.2f%n", + order.getId(), order.getCreatedAt(), order.getTotalPrice()); + } } -} \ No newline at end of file + /** Reads an int safely so bad input does not crash the program. */ + private int readInt() { + while (!scanner.hasNextInt()) { + System.out.print("Please enter a number: "); + scanner.next(); // throw away the bad token + } + return scanner.nextInt(); + } + +} diff --git a/src/main/java/dev/ui/ConsoleMenu.java b/src/main/java/dev/ui/ConsoleMenu.java index 663dc0c..106ccb0 100644 --- a/src/main/java/dev/ui/ConsoleMenu.java +++ b/src/main/java/dev/ui/ConsoleMenu.java @@ -1,5 +1,10 @@ package dev.ui; +import dev.model.User; +import dev.service.AuthService; +import dev.service.MenuService; +import dev.service.OrderService; + import java.util.Scanner; public class ConsoleMenu { @@ -7,8 +12,16 @@ public class ConsoleMenu { private final Scanner scanner = new Scanner(System.in); + private final AuthService authService = new AuthService(); + private final MenuService menuService = new MenuService(); + private final OrderService orderService = new OrderService(scanner); + public void start() { + System.out.println("======================================="); + System.out.println(" 🍕 WELCOME TO JAVA PIZZERIA 🍕"); + System.out.println("======================================="); + while (true) { System.out.println(); @@ -16,20 +29,22 @@ public class ConsoleMenu { System.out.println("1. Login"); System.out.println("2. Register"); System.out.println("3. Exit"); + System.out.print("Choose an option: "); - int choice = scanner.nextInt(); + int choice = readInt(); switch (choice) { case 1: - // TODO + handleLogin(); break; case 2: - // TODO + handleRegister(); break; case 3: + System.out.println("Goodbye!"); return; default: @@ -41,4 +56,95 @@ public class ConsoleMenu { } -} \ No newline at end of file + private void handleLogin() { + + System.out.print("Enter username: "); + String username = scanner.next(); + System.out.print("Enter password: "); + String password = scanner.next(); + + User user = authService.login(username, password); + + if (user == null) { + System.out.println("Login failed: wrong username or password."); + return; + } + + System.out.println("Welcome back, " + user.getUsername() + "!"); + showMainMenu(user); + } + + private void handleRegister() { + + System.out.print("Choose a username: "); + String username = scanner.next(); + System.out.print("Choose a password: "); + String password = scanner.next(); + System.out.print("Email (optional, or '-' to skip): "); + String email = scanner.next(); + if (email.equals("-")) { + email = null; + } + + boolean ok = authService.register(username, password, email); + if (ok) { + System.out.println("Account created! You can log in now."); + } else { + System.out.println("Registration failed. Please try again."); + } + } + + /** The menu shown after a successful login. */ + private void showMainMenu(User user) { + + while (true) { + + System.out.println(); + System.out.println("======================================="); + System.out.println(" 🍽️ MAIN MENU 🍽️"); + System.out.println("======================================="); + System.out.println("1. View Menu"); + System.out.println("2. Place a New Order"); + System.out.println("3. View Order History"); + System.out.println("4. Logout"); + System.out.print("Choose an option: "); + + int choice = readInt(); + + switch (choice) { + + case 1: + menuService.showMenu(); + break; + + case 2: + orderService.placeOrder(user.getId()); + break; + + case 3: + orderService.showOrderHistory(user.getId()); + break; + + case 4: + System.out.println("Logged out."); + return; + + default: + System.out.println("Invalid choice"); + + } + + } + + } + + /** Reads an int without crashing on bad input. */ + private int readInt() { + while (!scanner.hasNextInt()) { + System.out.print("Please enter a number: "); + scanner.next(); + } + return scanner.nextInt(); + } + +}