diff --git a/README.md b/README.md index d16da13..88d179c 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,16 @@ includes: For more details, see the [software requirements specification](docs/SOFTWARE_SPECS.md). -The code for the admin website is found in a separate repository. +This repository contains the code for the client website and the API. The code for the admin website is found in a +separate repository. ## Main features - MVC pattern - Semantic URL routing - Email-based password recovery -- Email notification on order -- Testing with phpUnit +- Email notification on checkout +- Integration testing with phpUnit - Mobile-responsive website - Utilizes Webpack for efficient code bundling and compatibility with older browsers. - Product review system with nested comments @@ -36,6 +37,7 @@ The code for the admin website is found in a separate repository. - Pagination - SEO optimized - REST API +- API testing with Guzzler and phpUnit ## Documentation @@ -58,3 +60,17 @@ Attribution-ShareAlike - https://github.com/kevinisaac/php-mvc 4. The filesystem was inspired by https://github.com/php-pds/sklseleton 5. Additional references are included within the code itself. + +# Todo + +Add `X-TEST-ENV` to the header of your request and set its value to `testing` if you want to use the testing database. +This is required when running tests for API. Without this key-value pair, the production database will be used. + +USE DOCKER PHP + +- read guzzle documentation. read base_uri +- test database is being used +- add variable to .env + +line 18 in Reviews API is redundant +use correct namespace \ No newline at end of file diff --git a/src/controllers/Orders.php b/src/controllers/Orders.php new file mode 100644 index 0000000..1cc1205 --- /dev/null +++ b/src/controllers/Orders.php @@ -0,0 +1,78 @@ +validateURL()) { + $url = Utility::getURL(); + $parts = explode('/', $url); + // Check if the last part of the URL is a valid integer + $lastPart = end($parts); + if (is_numeric($lastPart)) { + return (int)$lastPart; + } else { + return null; + } + } + return null; + } + + + private function handleInvalidURL(): void + { + if (!$this->validateURL()) { + (new Error())->handlePageNotFoundError(); + die(); + } + } + + public function index(): void + { + $this->handleInvalidURL(); + + $order_id = $this->getOrderIDFromURL(); + if ($order_id === null) { + (new Error())->handlePageNotFoundError(); + return; + } + + $order = Order::getByID($order_id); + if (!$order) { + (new Error())->handlePageNotFoundError(); + return; + } + + $order_products = Order::getOrderProducts($order->getOrderID()); + + $this->view_data['order'] = $order; + $this->view_data['line_items'] = $order_products; + + $this->view( + 'orders', + $this->view_data, + 'Order #' . $order_id, + enableIndexing: false + ); + } +} diff --git a/src/controllers/Profile.php b/src/controllers/Profile.php index f6c7892..e518acb 100644 --- a/src/controllers/Profile.php +++ b/src/controllers/Profile.php @@ -145,7 +145,7 @@ public function cancelOrder(): void } // Cancel the order - $order->deleteOrder(); + $order->cancelOrder(); } private function handleProfileEditSubmission(): void diff --git a/src/models/Order.php b/src/models/Order.php index c20cd86..07377be 100644 --- a/src/models/Order.php +++ b/src/models/Order.php @@ -260,21 +260,15 @@ public static function getByID(int $order_id): ?Order } /** - * Deletes the order and associated line items from the database. + * Cancels the order and associated line items from the database. */ - public function deleteOrder(): void + public function cancelOrder(): void { $conn = self::connect(); $conn->beginTransaction(); try { - // Delete line items first - $query = "DELETE FROM order_product WHERE order_id = :order_id"; - $stm = $conn->prepare($query); - $stm->execute(['order_id' => $this->order_id]); - - // Delete the order itself - $query = "DELETE FROM `order` WHERE order_id = :order_id"; + $query = "UPDATE `order` SET status = 'cancelled' WHERE order_id = :order_id"; $stm = $conn->prepare($query); $stm->execute(['order_id' => $this->order_id]); diff --git a/src/models/OrderProduct.php b/src/models/OrderProduct.php index 2c79e13..5c4d7be 100644 --- a/src/models/OrderProduct.php +++ b/src/models/OrderProduct.php @@ -87,29 +87,36 @@ public function validate(): array return $errors; } - public static function getByID(int $order_id, int $product_id): ?OrderProduct + public static function getByID(int $order_id, int $product_id = null): ?OrderProduct { - $query = <<< EOL - select * from order_product - where order_id = :order_id and product_id = :product_id - EOL; + $query = 'SELECT * FROM order_product WHERE order_id = :order_id'; + $params = ['order_id' => $order_id]; - $result = self::query($query, ['order_id' => $order_id, 'product_id' => $product_id]); - if (empty($result)) { - return null; - } - $result = $result[0]; + if ($product_id !== null) { + $query .= ' AND product_id = :product_id'; + $params['product_id'] = $product_id; + } - return new OrderProduct( - product_id: $result->product_id, - cup_size: $result->cup_size, - milk_type: $result->milk_type, - quantity: $result->quantity, - unit_price: (float)$result->unit_price, - order_id: $result->order_id, - ); + $result = self::query($query, $params); + if (empty($result)) { + return null; } + // Assuming there's only one product for a given order if product_id is not provided + if ($product_id === null) { + $result = $result[0]; + } + + return new OrderProduct( + product_id: $result->product_id, + cup_size: $result->cup_size, + milk_type: $result->milk_type, + quantity: $result->quantity, + unit_price: (float)$result->unit_price, + order_id: $result->order_id, + ); + } + public function getOrderID(): int { return $this->order_id; diff --git a/src/views/Orders.php b/src/views/Orders.php new file mode 100644 index 0000000..a51e937 --- /dev/null +++ b/src/views/Orders.php @@ -0,0 +1,48 @@ + + +
+

Order #getOrderID(), FILTER_SANITIZE_NUMBER_INT); ?>

+
+

Order Details

+

Order ID: getOrderID(), FILTER_SANITIZE_NUMBER_INT); ?>

+

Date: getCreatedDate()->format('Y-m-d H:i:s')) ?>

+

Status: getStatus()->value)) ?>

+

Total Price: $calculateTotalPrice(), 2)) ?>

+
+ +
+

Order Items

+ + + + + + + + + + + + + + + + + +
Product NameQuantityMilk TypeCup SizeUnit Price
getProductName()) ?>getQuantity(), FILTER_SANITIZE_NUMBER_INT) ?>getMilkType()) ?>getCupSize()) ?>$getUnitPrice(), 2)) ?>
+
+
diff --git a/src/views/Profile.php b/src/views/Profile.php index 09fd683..3987139 100644 --- a/src/views/Profile.php +++ b/src/views/Profile.php @@ -100,7 +100,7 @@ $totalPrice = htmlspecialchars(number_format($order->calculateTotalPrice(), 2)); // Determine button states - $cancelDisabled = $order->getStatus()->value === 'completed' ? 'disabled' : ''; + $cancelDisabled = ($order->getStatus()->value === 'completed' || $order->getStatus()->value === 'cancelled') ? 'disabled' : ''; echo <<< EOL @@ -112,17 +112,22 @@
+ View - - + +
EOL; } + ?> + + +
@@ -169,9 +174,10 @@
+
- + \ No newline at end of file