From a08c4eba3b94d2cc4b8702b01e148548249bb263 Mon Sep 17 00:00:00 2001 From: Hickmeister <35031453+Hickmeister@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:28:59 +0000 Subject: [PATCH] I cba with this commit message tbh --- assets/css/app.css | 6 +- assets/css/bootstrap-extended.css | 50 +-- assets/plugins/chartjs/js/chartjs-custom.js | 14 +- assets/plugins/morris/js/morris-data.js | 10 +- assets/sass/app.css | 6 +- assets/sass/app.scss | 6 +- public/addFilament.php | 2 +- public/filamentDryer.php | 2 +- public/index.php | 2 +- public/login.php | 7 +- public/printerForm.php | 2 +- public/shipping.php | 265 +++++++++++++++ public/userManagement.php | 319 +++++++++++++++++++ public/viewFilament.php | 153 ++++----- public/viewPrinters.php | 2 +- src/filamentTracker/addFilament.php | 78 ++--- src/filamentTracker/getFilamentPrices.php | 12 +- src/filamentTracker/scraper.php | 46 +++ src/filamentTracker/updateFilamentPrices.php | 39 +-- src/header.php | 2 + src/nav.php | 10 + src/shipping/get_account_info.php | 74 +++++ src/shipping/get_orders_detail.php | 73 +++++ src/shipping/get_prepay_balance.php | 73 +++++ src/userMananagementService.php | 111 +++++++ template.php | 2 +- 26 files changed, 1135 insertions(+), 231 deletions(-) create mode 100644 public/shipping.php create mode 100644 public/userManagement.php create mode 100644 src/filamentTracker/scraper.php create mode 100644 src/shipping/get_account_info.php create mode 100644 src/shipping/get_orders_detail.php create mode 100644 src/shipping/get_prepay_balance.php create mode 100644 src/userMananagementService.php diff --git a/assets/css/app.css b/assets/css/app.css index e1504ac..ef65652 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -3780,7 +3780,7 @@ form select.error:focus { .btn-inverse-success { - color: #15ca20; + color: #1aad24; background-color: rgba(21, 202, 32, 0.18); @@ -3792,7 +3792,7 @@ form select.error:focus { .btn-inverse-success:hover { - color: #15ca20; + color: #1aad24; background-color: rgba(21, 202, 32, 0.18); @@ -3802,7 +3802,7 @@ form select.error:focus { .btn-inverse-success:focus { - color: #15ca20; + color: #1aad24; background-color: rgba(21, 202, 32, 0.18); diff --git a/assets/css/bootstrap-extended.css b/assets/css/bootstrap-extended.css index 19ec965..d494755 100644 --- a/assets/css/bootstrap-extended.css +++ b/assets/css/bootstrap-extended.css @@ -23,33 +23,33 @@ a { } .valid-feedback { - color: #15ca20 + color: #23ac2c } .form-control.is-valid, .was-validated .form-control:valid { - border-color: #15ca20; + border-color: #23ac2c; } .form-control.is-valid:focus, .was-validated .form-control:valid:focus { - border-color: #15ca20; + border-color: #23ac2c; } .form-select.is-valid, .was-validated .form-select:valid { - border-color: #15ca20; + border-color: #23ac2c; } .form-select.is-valid:focus, .was-validated .form-select:valid:focus { - border-color: #15ca20; + border-color: #23ac2c; } .form-check-input.is-valid, .was-validated .form-check-input:valid { - border-color: #15ca20 + border-color: #23ac2c } .form-check-input.is-valid:checked, .was-validated .form-check-input:valid:checked { - background-color: #15ca20 + background-color: #23ac2c } .form-check-input.is-valid~.form-check-label, .was-validated .form-check-input:valid~.form-check-label { - color: #15ca20 + color: #23ac2c } .invalid-feedback { @@ -120,8 +120,8 @@ a { .btn-success { color: #fff; - background-color: #15ca20; - border-color: #15ca20 + background-color: #2375ac; + border-color: #23ac2c } .btn-success:hover { color: #fff; @@ -207,27 +207,27 @@ a { .btn-outline-success { - color: #15ca20; - border-color: #15ca20 + color: #23ac2c; + border-color: #23ac2c } .btn-outline-success:hover { color: #fff; - background-color: #15ca20; - border-color: #15ca20 + background-color: #23ac2c; + border-color: #23ac2c } .btn-check:focus+.btn-outline-success, .btn-outline-success:focus { box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%) } .btn-check:active+.btn-outline-success, .btn-check:checked+.btn-outline-success, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show, .btn-outline-success:active { color: #fff; - background-color: #15ca20; - border-color: #15ca20 + background-color: #23ac2c; + border-color: #23ac2c } .btn-check:active+.btn-outline-success:focus, .btn-check:checked+.btn-outline-success:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus, .btn-outline-success:active:focus { box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%) } .btn-outline-success.disabled, .btn-outline-success:disabled { - color: #15ca20; + color: #23ac2c; background-color: transparent } @@ -304,7 +304,7 @@ a { } .border-success { - border-color: #15ca20!important + border-color: #23ac2c!important } .border-danger { @@ -324,7 +324,7 @@ a { color: #6c757d!important } .text-success { - color: #15ca20!important + color: #23ac2c!important } .text-info { color: #0dcaf0!important @@ -364,7 +364,7 @@ a { } .bg-success { - background-color: #15ca20 !important; + background-color: #23ac2c !important; } .bg-danger { @@ -373,19 +373,19 @@ a { .form-check-success .form-check-input:checked { - background-color: #15ca20; - border-color: #15ca20 + background-color: #23ac2c; + border-color: #23ac2c } .form-check-success .form-check-input[type=checkbox]:indeterminate { - background-color: #15ca20; - border-color: #15ca20; + background-color: #23ac2c; + border-color: #23ac2c; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e") } .form-check-success .form-check-input:focus { - border-color: #15ca20; + border-color: #23ac2c; outline: 0; box-shadow: 0 0 0 .25rem rgba(21, 202, 33, 0.25) } diff --git a/assets/plugins/chartjs/js/chartjs-custom.js b/assets/plugins/chartjs/js/chartjs-custom.js index d5cf4d1..0bfe39d 100644 --- a/assets/plugins/chartjs/js/chartjs-custom.js +++ b/assets/plugins/chartjs/js/chartjs-custom.js @@ -72,11 +72,11 @@ $(function() { label: 'Facebook', data: [12, 30, 16, 23, 8, 14, 11], backgroundColor: [ - '#15ca20' + '#23ac2c' ], tension: 0, borderColor: [ - '#15ca20' + '#23ac2c' ], borderWidth: 3 }] @@ -113,7 +113,7 @@ $(function() { '#6f42c1', '#d63384', '#fd7e14', - '#15ca20', + '#23ac2c', '#0dcaf0' ], borderWidth: 1.5 @@ -146,7 +146,7 @@ $(function() { '#6f42c1', '#d63384', '#fd7e14', - '#15ca20', + '#23ac2c', '#0dcaf0' ], borderWidth: 1 @@ -242,7 +242,7 @@ $(function() { '#6f42c1', '#d63384', '#fd7e14', - '#15ca20', + '#23ac2c', '#0dcaf0' ], }] @@ -372,7 +372,7 @@ var myChart = new Chart(ctx, { label: 'Facebook', data: [5, 30, 16, 23, 8, 14, 2], backgroundColor: [ - '#15ca20' + '#23ac2c' ], fill: { target: 'origin', @@ -381,7 +381,7 @@ var myChart = new Chart(ctx, { }, tension: 0.4, borderColor: [ - '#15ca20' + '#23ac2c' ], borderWidth: 4 }] diff --git a/assets/plugins/morris/js/morris-data.js b/assets/plugins/morris/js/morris-data.js index dc56524..ec68844 100644 --- a/assets/plugins/morris/js/morris-data.js +++ b/assets/plugins/morris/js/morris-data.js @@ -41,12 +41,12 @@ Morris.Area({ labels: ['iPhone', 'iPad'], pointSize: 3, fillOpacity: 0, - pointStrokeColors:['#008cff', '#15ca20'], + pointStrokeColors:['#008cff', '#23ac2c'], behaveLikeLine: true, gridLineColor: '#e0e0e0', lineWidth: 3, hideHover: 'auto', - lineColors: ['#008cff', '#15ca20'], + lineColors: ['#008cff', '#23ac2c'], resize: true }); @@ -67,7 +67,7 @@ Morris.Area({ value: 20 }], resize: true, - colors:['#008cff', '#15ca20', '#fd3550'] + colors:['#008cff', '#23ac2c', '#fd3550'] }); // Morris bar chart @@ -112,7 +112,7 @@ Morris.Area({ xkey: 'y', ykeys: ['a', 'b', 'c'], labels: ['A', 'B', 'C'], - barColors:['#008cff', '#15ca20', '#75808a'], + barColors:['#008cff', '#23ac2c', '#75808a'], hideHover: 'auto', gridLineColor: '#eef0f2', resize: true @@ -153,7 +153,7 @@ Morris.Area({ ], - lineColors: ['#008cff', '#15ca20'], + lineColors: ['#008cff', '#23ac2c'], xkey: 'period', ykeys: ['iphone', 'ipad'], labels: ['Site A', 'Site B'], diff --git a/assets/sass/app.css b/assets/sass/app.css index 6408480..50994fa 100644 --- a/assets/sass/app.css +++ b/assets/sass/app.css @@ -2130,17 +2130,17 @@ form select.error:focus { } .btn-inverse-success { - color: #15ca20; + color: #23ac2c; background-color: rgba(21, 202, 32, 0.18); border-color: rgb(212, 246, 214); } .btn-inverse-success:hover { - color: #15ca20; + color: #23ac2c; background-color: rgba(21, 202, 32, 0.18); border-color: rgba(21, 202, 32, 0.18); } .btn-inverse-success:focus { - color: #15ca20; + color: #23ac2c; background-color: rgba(21, 202, 32, 0.18); border-color: rgba(21, 202, 32, 0.18); box-shadow: 0 0 0 0.25rem rgba(23, 160, 14, 0.32); diff --git a/assets/sass/app.scss b/assets/sass/app.scss index 06d2e51..ee00445 100644 --- a/assets/sass/app.scss +++ b/assets/sass/app.scss @@ -2582,18 +2582,18 @@ form select.error:focus { } .btn-inverse-success { - color: #15ca20; + color: #23ac2c; background-color: rgba(21, 202, 32, 0.18); border-color: rgb(212, 246, 214); &:hover { - color: #15ca20; + color: #23ac2c; background-color: rgba(21, 202, 32, 0.18); border-color: rgba(21, 202, 32, 0.18); } &:focus { - color: #15ca20; + color: #23ac2c; background-color: rgba(21, 202, 32, 0.18); border-color: rgba(21, 202, 32, 0.18); box-shadow: 0 0 0 .25rem rgb(23 160 14 / 32%); diff --git a/public/addFilament.php b/public/addFilament.php index a8b3e72..2abbe7c 100644 --- a/public/addFilament.php +++ b/public/addFilament.php @@ -2,7 +2,7 @@ checkUserRole(['admin']); ?> - + diff --git a/public/filamentDryer.php b/public/filamentDryer.php index 666fbca..013c8cf 100644 --- a/public/filamentDryer.php +++ b/public/filamentDryer.php @@ -2,7 +2,7 @@ checkUserRole(['admin']); ?> - + diff --git a/public/index.php b/public/index.php index 2cd7a0f..c93365d 100644 --- a/public/index.php +++ b/public/index.php @@ -1,6 +1,6 @@ - + diff --git a/public/login.php b/public/login.php index 2f999f5..104e8ac 100644 --- a/public/login.php +++ b/public/login.php @@ -1,5 +1,5 @@ - + @@ -14,7 +14,10 @@ + + + TOD - Admin Dashboard @@ -40,7 +43,7 @@
- +
diff --git a/public/printerForm.php b/public/printerForm.php index 83cd36a..17b8dba 100644 --- a/public/printerForm.php +++ b/public/printerForm.php @@ -1,6 +1,6 @@ - + diff --git a/public/shipping.php b/public/shipping.php new file mode 100644 index 0000000..132d460 --- /dev/null +++ b/public/shipping.php @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TOD Dashboard + + + + +
+ + + + + + + +
+
+ +
+
+ + +
+ +
+
+
+
+
+ +
+
+
Loading...
+

Account Information

+
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
Loading...
+

Prepay Balance

+
+
+
+
+
+ + +
+
+
+
Orders
+
+ + + + + + + + + + + + + + + + +
Order IDCourierServiceTracking StatusEstimated DeliveryRecipient
Loading orders...
+
+
+
+
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/userManagement.php b/public/userManagement.php new file mode 100644 index 0000000..62af988 --- /dev/null +++ b/public/userManagement.php @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TOD Dashboard + + + + +
+ + + + + + + + +
+
+ + + + + +
+

User Management

+ +
+ +
+ +
+ + + + + + + + + + + + + +
IDUsernameEmailRoleActions
+
+ + + + + + +
+ + + + + + +
+
+ + + +
+ + + + +
+

Copyright © 2024. All right reserved.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/viewFilament.php b/public/viewFilament.php index 472f272..6ac14fc 100644 --- a/public/viewFilament.php +++ b/public/viewFilament.php @@ -1,6 +1,6 @@ - + @@ -139,16 +139,6 @@ document.addEventListener("DOMContentLoaded", function () { `; } - // Debounce function to reduce DOM updates - function debounceRender(func, delay) { - let inDebounce; - return function() { - const context = this, args = arguments; - clearTimeout(inDebounce); - inDebounce = setTimeout(() => func.apply(context, args), delay); - }; - } - fetch('../src/filamentTracker/getFilamentPrices.php') .then(response => response.json()) .then(data => { @@ -159,36 +149,56 @@ document.addEventListener("DOMContentLoaded", function () { const prices = data.data[filament].prices.map(entry => entry.price); const timestamps = data.data[filament].prices.map(entry => entry.recordedAt); const latestPrice = prices[prices.length - 1] || 0; - const previousPrice = prices[prices.length - 2] || latestPrice; - const priceDifference = latestPrice - previousPrice; - + + // Find the last distinct price (where the price actually changed) + let lastDistinctPrice = latestPrice; + for (let i = prices.length - 2; i >= 0; i--) { + if (prices[i] !== latestPrice) { + lastDistinctPrice = prices[i]; + break; + } + } + + const priceDifference = latestPrice - lastDistinctPrice; + + // Price Change Indicator let priceChangeIndicator; if (priceDifference > 0) { priceChangeIndicator = ` +£${priceDifference.toFixed(2)}`; } else if (priceDifference < 0) { priceChangeIndicator = ` - £${Math.abs(priceDifference).toFixed(2)}`; + -£${Math.abs(priceDifference).toFixed(2)}`; } else { priceChangeIndicator = ` £0.00`; } const amazonUrl = data.data[filament].amazonUrl || '#'; + const discount = data.data[filament].currentDiscount || 0; // Get discount percentage const chartId = `chart-${filament.replace(/\s+/g, '-')}`; const chartColor = getRandomColor(); - // Create Card HTML with cart button + // Create Card HTML const cardHTML = `
- ${filament} + ${filament}
£${latestPrice} ${priceChangeIndicator}
+
+ ${ + discount > 0 + ? ` + -${discount}% + ` + : '' + } +
@@ -197,84 +207,38 @@ document.addEventListener("DOMContentLoaded", function () { container.insertAdjacentHTML('beforeend', cardHTML); - // Observer to Wait for DOM Insertion - const observer = new MutationObserver(debounceRender(() => { - const chartElement = document.getElementById(chartId); - if (chartElement) { - const chartOptions = { - series: [{ - name: "Price", - data: prices - }], - chart: { - type: "area", - height: 110, - toolbar: { - show: false - }, - zoom: { - enabled: false - }, - dropShadow: { - enabled: true, - top: 3, - left: 14, - blur: 4, - opacity: 0.12, - color: chartColor - }, - sparkline: { - enabled: true - } + // Chart Rendering + const chartOptions = { + series: [{ name: "Price", data: prices }], + chart: { + type: "area", + height: 110, + toolbar: { show: false }, + zoom: { enabled: false }, + sparkline: { enabled: true }, + }, + markers: { size: 0 }, + dataLabels: { enabled: false }, + stroke: { show: true, width: 2.4, curve: "smooth" }, + colors: [chartColor], + xaxis: { categories: timestamps, labels: { show: false } }, + fill: { opacity: 1 }, + tooltip: { + theme: "dark", + y: { + formatter: function (value, { dataPointIndex }) { + const date = new Date(timestamps[dataPointIndex]); + return `£${value} - ${date.toLocaleDateString()}`; }, - markers: { - size: 0, - colors: [chartColor], - strokeColors: "#fff", - strokeWidth: 2, - hover: { - size: 7 - } - }, - dataLabels: { - enabled: false - }, - stroke: { - show: true, - width: 2.4, - curve: "smooth" - }, - colors: [chartColor], - xaxis: { - categories: timestamps, - labels: { - show: false - } - }, - fill: { - opacity: 1 - }, - tooltip: { - theme: "dark", - x: { - show: false - }, - y: { - formatter: function (value, { dataPointIndex }) { - const date = new Date(timestamps[dataPointIndex]); - return `£${value} - ${date.toLocaleDateString()}`; - } - } - } - }; + }, + }, + }; - const chart = new ApexCharts(chartElement, chartOptions); - chart.render(); - observer.disconnect(); - } - }, 300)); - - observer.observe(container, { childList: true, subtree: true }); + const chartElement = document.getElementById(chartId); + if (chartElement) { + const chart = new ApexCharts(chartElement, chartOptions); + chart.render(); + } }); } else { container.innerHTML = '

No filament data available.

'; @@ -288,6 +252,9 @@ document.addEventListener("DOMContentLoaded", function () { }); + + + \ No newline at end of file diff --git a/public/viewPrinters.php b/public/viewPrinters.php index 361da5e..eedae8f 100644 --- a/public/viewPrinters.php +++ b/public/viewPrinters.php @@ -1,6 +1,6 @@ - + diff --git a/src/filamentTracker/addFilament.php b/src/filamentTracker/addFilament.php index f1d8197..ef433ee 100644 --- a/src/filamentTracker/addFilament.php +++ b/src/filamentTracker/addFilament.php @@ -1,88 +1,73 @@ 'error', 'message' => 'User not authenticated.']); exit; } -// Check for POST data if ($_SERVER['REQUEST_METHOD'] === 'POST') { $filamentName = $_POST['productName'] ?? ''; $amazonUrl = $_POST['productUrl'] ?? ''; $userId = $_SESSION['userId']; - // Basic validation if (empty($filamentName) || empty($amazonUrl)) { echo json_encode(['status' => 'error', 'message' => 'Filament name and URL are required.']); exit; } - $client = new Client(); try { - // Scrape the Amazon page for initial price and other details - $crawler = $client->request('GET', $amazonUrl); - - // Scrape Price - $whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0'; - $fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00'; + // Scrape data from Amazon + $scrapedData = scrapeAmazonData($amazonUrl); + if (!$scrapedData) { + throw new Exception('Scraping failed for the provided URL.'); + } - $whole = preg_replace('/[^0-9]/', '', $whole); - $fraction = preg_replace('/[^0-9]/', '', $fraction); - $fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2); - $totalPrice = floatval($whole . '.' . $fraction); + $price = $scrapedData['price']; + $discount = $scrapedData['discount']; + $details = $scrapedData['details']; - // Scrape Filament Details from Table - $details = []; - $crawler->filter('table.a-normal tr')->each(function ($node) use (&$details) { - $label = trim($node->filter('td.a-span3')->text()); - $value = trim($node->filter('td.a-span9')->text()); - $details[$label] = $value; - }); - - - // Extract details or use default if not found + // Extract details or use defaults $brand = $details['Brand'] ?? 'Unknown'; $material = $details['Material'] ?? 'Unknown'; $color = $details['Colour'] ?? 'Unknown'; $filamentWeight = isset($details['Item weight']) ? preg_replace('/[^0-9.]/', '', $details['Item weight']) : 1; if (stripos($details['Item weight'], 'kilograms') !== false || stripos($details['Item weight'], 'kg') !== false) { - $filamentWeight = $filamentWeight * 1000; // Convert KG to G + $filamentWeight = $filamentWeight * 1000; // Convert KG to G } $itemDiameter = isset($details['Item diameter']) ? preg_replace('/[^0-9.]/', '', $details['Item diameter']) : 1.75; - // Start a transaction to ensure consistency + // Database operations $pdo->beginTransaction(); - - // Check if filament already exists $stmt = $pdo->prepare("SELECT id FROM filamentTracker WHERE amazonUrl = :amazonUrl"); $stmt->execute([':amazonUrl' => $amazonUrl]); $existingFilament = $stmt->fetch(PDO::FETCH_ASSOC); if ($existingFilament) { - // Filament exists – just update the price history $filamentId = $existingFilament['id']; $stmt = $pdo->prepare(" - INSERT INTO filamentPriceHistory (filamentId, price) - VALUES (:filamentId, :price) + INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount) + VALUES (:filamentId, :price, :currentDiscount) "); $stmt->execute([ ':filamentId' => $filamentId, - ':price' => $totalPrice + ':price' => $price, + ':currentDiscount' => $discount ]); - $message = 'Filament price updated successfully.'; + $message = 'Filament price and discount updated successfully.'; } else { - // Insert new filament into filamentTracker $stmt = $pdo->prepare(" INSERT INTO filamentTracker (userId, filamentName, amazonUrl, filamentWeight, brand, material, color, itemDiameter) VALUES (:userId, :filamentName, :amazonUrl, :filamentWeight, :brand, :material, :color, :itemDiameter) @@ -98,29 +83,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ':itemDiameter' => $itemDiameter ]); - // Get the last inserted filament ID $filamentId = $pdo->lastInsertId(); - - // Insert initial price into filamentPriceHistory $stmt = $pdo->prepare(" - INSERT INTO filamentPriceHistory (filamentId, price) - VALUES (:filamentId, :price) + INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount) + VALUES (:filamentId, :price, :currentDiscount) "); $stmt->execute([ ':filamentId' => $filamentId, - ':price' => $totalPrice + ':price' => $price, + ':currentDiscount' => $discount ]); - - $message = $filamentName . ' Filament added successfully and price tracked.'; + $message = "$filamentName added successfully and price with discount tracked."; } - // Commit the transaction $pdo->commit(); - echo json_encode(['status' => 'success', 'message' => $message]); } catch (Exception $e) { $pdo->rollBack(); - echo json_encode(['status' => 'error', 'message' => 'Failed to scrape or insert data. Error: ' . $e->getMessage()]); + echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); } } else { echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']); diff --git a/src/filamentTracker/getFilamentPrices.php b/src/filamentTracker/getFilamentPrices.php index ef788e4..cd269dd 100644 --- a/src/filamentTracker/getFilamentPrices.php +++ b/src/filamentTracker/getFilamentPrices.php @@ -9,7 +9,7 @@ include '../src/session_check.php'; header('Content-Type: application/json'); try { - // Fetch all filament prices with a limit of 180 entries per filament using JOIN + // Fetch all filament prices with the latest discount $stmt = $pdo->query(" SELECT ft.filamentName, ft.brand, @@ -17,6 +17,7 @@ try { ft.color, ft.amazonUrl, fp.price, + fp.currentDiscount, fp.recordedAt FROM filamentTracker ft JOIN filamentPriceHistory fp ON ft.id = fp.filamentId @@ -27,11 +28,11 @@ try { ROW_NUMBER() OVER (PARTITION BY filamentId ORDER BY recordedAt DESC) as rn FROM filamentPriceHistory ) ranked - WHERE rn <= 180 + WHERE rn <= 280 ) filtered ON fp.filamentId = filtered.filamentId AND fp.recordedAt = filtered.recordedAt ORDER BY ft.filamentName, fp.recordedAt ASC "); - + $filaments = $stmt->fetchAll(PDO::FETCH_ASSOC); $result = []; @@ -39,14 +40,15 @@ try { // Format data for charts (grouped by filament) foreach ($filaments as $filament) { $name = $filament['filamentName']; - + if (!isset($result[$name])) { $result[$name] = [ 'brand' => $filament['brand'], 'material' => $filament['material'], 'color' => $filament['color'], 'amazonUrl' => $filament['amazonUrl'], - 'prices' => [] + 'prices' => [], + 'currentDiscount' => $filament['currentDiscount'] ?? 0 // Include the latest discount ]; } diff --git a/src/filamentTracker/scraper.php b/src/filamentTracker/scraper.php new file mode 100644 index 0000000..43b3bb0 --- /dev/null +++ b/src/filamentTracker/scraper.php @@ -0,0 +1,46 @@ +request('GET', $url); + + // Scrape price + $whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0'; + $fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00'; + $whole = preg_replace('/[^0-9]/', '', $whole); + $fraction = preg_replace('/[^0-9]/', '', $fraction); + $fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2); + $totalPrice = floatval($whole . '.' . $fraction); + + // Scrape discount + $discount = $crawler->filter('.savingsPercentage')->count() + ? $crawler->filter('.savingsPercentage')->text() + : null; + if ($discount) { + $discount = preg_replace('/[^0-9]/', '', $discount); + } else { + $discount = 0; // Default to 0 if no discount is found + } + + // Scrape additional details + $details = []; + $crawler->filter('table.a-normal tr')->each(function ($node) use (&$details) { + $label = trim($node->filter('td.a-span3')->text()); + $value = trim($node->filter('td.a-span9')->text()); + $details[$label] = $value; + }); + + return [ + 'price' => $totalPrice, + 'discount' => $discount, + 'details' => $details + ]; + } catch (Exception $e) { + return null; // Return null if scraping fails + } +} diff --git a/src/filamentTracker/updateFilamentPrices.php b/src/filamentTracker/updateFilamentPrices.php index ee5ad45..3ef4336 100644 --- a/src/filamentTracker/updateFilamentPrices.php +++ b/src/filamentTracker/updateFilamentPrices.php @@ -2,31 +2,9 @@ require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php'; require '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/db.php'; require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php'; +require 'scraper.php'; loadEnv(__DIR__ . '/../../.env'); -use Goutte\Client; - -// Function to scrape price -function scrapePrice($url) { - $client = new Client(); // Reinitialize client for each request - try { - $crawler = $client->request('GET', $url); - - $whole = $crawler->filter('.a-price-whole')->count() ? $crawler->filter('.a-price-whole')->text() : '0'; - $fraction = $crawler->filter('.a-price-fraction')->count() ? $crawler->filter('.a-price-fraction')->text() : '00'; - - $whole = preg_replace('/[^0-9]/', '', $whole); - $fraction = preg_replace('/[^0-9]/', '', $fraction); - $fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2); - - $totalPrice = floatval($whole . '.' . $fraction); - - return $totalPrice; - } catch (Exception $e) { - return null; - } -} - // Fetch all filaments try { $stmt = $pdo->query("SELECT * FROM filamentTracker"); @@ -36,20 +14,21 @@ try { $amazonUrl = $filament['amazonUrl']; $filamentId = $filament['id']; - // Scrape price - $totalPrice = scrapePrice($amazonUrl); + // Use the scraper function to fetch price and discount + $scrapedData = scrapeAmazonData($amazonUrl); - if ($totalPrice !== null && $totalPrice > 0) { + if ($scrapedData && $scrapedData['price'] > 0) { $stmt = $pdo->prepare(" - INSERT INTO filamentPriceHistory (filamentId, price) - VALUES (:filamentId, :price) + INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount) + VALUES (:filamentId, :price, :currentDiscount) "); $stmt->execute([ ':filamentId' => $filamentId, - ':price' => $totalPrice + ':price' => $scrapedData['price'], + ':currentDiscount' => $scrapedData['discount'] ]); - echo "Updated price for {$filament['filamentName']}: £{$totalPrice}\n"; + echo "Updated price for {$filament['filamentName']}: £{$scrapedData['price']}, Discount: {$scrapedData['discount']}%\n"; } else { echo "Failed to update {$filament['filamentName']} (no price found or £0).\n"; } diff --git a/src/header.php b/src/header.php index 09bcff6..6a26acc 100644 --- a/src/header.php +++ b/src/header.php @@ -67,6 +67,8 @@
  • Add Printer
  • +
  • user Management +
  • diff --git a/src/nav.php b/src/nav.php index 5807e56..0c2659d 100644 --- a/src/nav.php +++ b/src/nav.php @@ -21,6 +21,16 @@ + + +
  • + +
    +
    + +
    +
  • +
  • diff --git a/src/shipping/get_account_info.php b/src/shipping/get_account_info.php new file mode 100644 index 0000000..2ac19c2 --- /dev/null +++ b/src/shipping/get_account_info.php @@ -0,0 +1,74 @@ + 'client_credentials', + 'scope' => 'public-api', + 'client_id' => $client_id, + 'client_secret' => $client_secret, + ]); + + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/x-www-form-urlencoded', + 'Accept: */*', + ]); + + $response = curl_exec($ch); + curl_close($ch); + + $result = json_decode($response, true); + return $result['access_token'] ?? null; +} + +// Function to fetch account information +function getAccountInfo($token, $account_info_url) { + $ch = curl_init($account_info_url); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer $token", + 'Accept: application/json', + ]); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); +} + +// Main logic +$token = getApiToken($client_id, $client_secret, $auth_url); +$account_info = getAccountInfo($token, $account_info_url); + +if ($token) { + $account_info = getAccountInfo($token, $account_info_url); + + if ($account_info) { + echo json_encode(['success' => true, 'data' => $account_info]); + } else { + http_response_code(402); + echo json_encode(['success' => false, 'error' => 'Failed to fetch account information.']); + } +} else { + http_response_code(402); + echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']); +} +?> diff --git a/src/shipping/get_orders_detail.php b/src/shipping/get_orders_detail.php new file mode 100644 index 0000000..adaf174 --- /dev/null +++ b/src/shipping/get_orders_detail.php @@ -0,0 +1,73 @@ + 'client_credentials', + 'scope' => 'public-api', + 'client_id' => $client_id, + 'client_secret' => $client_secret, + ]); + + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/x-www-form-urlencoded', + 'Accept: */*', + ]); + + $response = curl_exec($ch); + curl_close($ch); + + $result = json_decode($response, true); + return $result['access_token'] ?? null; +} + +// Function to fetch order details +function getOrdersDetail($token, $orders_detail_url) { + $ch = curl_init($orders_detail_url); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer $token", + 'Accept: application/json', + ]); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); +} + +// Main logic +$token = getApiToken($client_id, $client_secret, $auth_url); + +if ($token) { + $orders_detail = getOrdersDetail($token, $orders_detail_url); + + if ($orders_detail) { + echo json_encode(['success' => true, 'data' => $orders_detail]); + } else { + http_response_code(402); + echo json_encode(['success' => false, 'error' => 'Failed to fetch order details.']); + } +} else { + http_response_code(402); + echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']); +} +?> diff --git a/src/shipping/get_prepay_balance.php b/src/shipping/get_prepay_balance.php new file mode 100644 index 0000000..c2bf570 --- /dev/null +++ b/src/shipping/get_prepay_balance.php @@ -0,0 +1,73 @@ + 'client_credentials', + 'scope' => 'public-api', + 'client_id' => $client_id, + 'client_secret' => $client_secret, + ]); + + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/x-www-form-urlencoded', + 'Accept: */*', + ]); + + $response = curl_exec($ch); + curl_close($ch); + + $result = json_decode($response, true); + return $result['access_token'] ?? null; +} + +// Function to fetch prepay balance +function getPrepayBalance($token, $prepay_balance_url) { + $ch = curl_init($prepay_balance_url); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer $token", + 'Accept: application/json', + ]); + + $response = curl_exec($ch); + curl_close($ch); + + return json_decode($response, true); +} + +// Main logic +$token = getApiToken($client_id, $client_secret, $auth_url); + +if ($token) { + $balance_info = getPrepayBalance($token, $prepay_balance_url); + + if ($balance_info) { + echo json_encode(['success' => true, 'data' => $balance_info]); + } else { + http_response_code(402); + echo json_encode(['success' => false, 'error' => 'Failed to fetch prepay balance.']); + } +} else { + http_response_code(402); + echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']); +} +?> diff --git a/src/userMananagementService.php b/src/userMananagementService.php new file mode 100644 index 0000000..11488c2 --- /dev/null +++ b/src/userMananagementService.php @@ -0,0 +1,111 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} catch (PDOException $e) { + die("Database connection failed: " . $e->getMessage()); +} + +// Handle requests +$method = $_SERVER['REQUEST_METHOD']; + +if ($method === 'GET') { + fetchUsers(); +} elseif ($method === 'POST') { + $input = json_decode(file_get_contents('php://input'), true); + if (isset($input['username']) && isset($input['email']) && isset($input['role']) && isset($input['password'])) { + createUser($input); + } elseif (isset($_GET['reset-password'])) { + $userId = intval($_GET['reset-password']); + resetPassword($userId, $input); + } elseif (isset($_GET['delete-user'])) { + $userId = intval($_GET['delete-user']); + deleteUser($userId); + } else { + http_response_code(400); + echo json_encode(["error" => "Invalid request"]); + } +} else { + http_response_code(405); + echo json_encode(["error" => "Method not allowed"]); +} + +function fetchUsers() +{ + global $pdo; + $stmt = $pdo->prepare("SELECT id, username, email, role, disabled, created_at FROM users"); + $stmt->execute(); + $users = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode($users); +} + +function createUser($input) +{ + global $pdo; + + $hashedPassword = password_hash($input['password'], PASSWORD_BCRYPT); + + $stmt = $pdo->prepare("INSERT INTO users (username, email, role, password, disabled, created_at) VALUES (:username, :email, :role, :password, 0, NOW())"); + + if ($stmt->execute([ + ':username' => $input['username'], + ':email' => $input['email'], + ':role' => $input['role'], + ':password' => $hashedPassword + ])) { + http_response_code(201); + echo json_encode(["success" => "User created successfully"]); + } else { + http_response_code(500); + echo json_encode(["error" => "Failed to create user"]); + } +} + +function resetPassword($userId, $input) +{ + global $pdo; + + if (!isset($input['password'])) { + http_response_code(400); + echo json_encode(["error" => "Password not provided"]); + return; + } + + $newPassword = password_hash($input['password'], PASSWORD_BCRYPT); + + $stmt = $pdo->prepare("UPDATE users SET password = :password WHERE id = :id"); + + if ($stmt->execute([ + ':password' => $newPassword, + ':id' => $userId + ])) { + echo json_encode(["success" => "Password reset successfully"]); + } else { + http_response_code(500); + echo json_encode(["error" => "Failed to reset password"]); + } +} + +function deleteUser($userId) +{ + global $pdo; + + $stmt = $pdo->prepare("DELETE FROM users WHERE id = :id"); + + if ($stmt->execute([':id' => $userId])) { + echo json_encode(["success" => "User deleted successfully"]); + } else { + http_response_code(500); + echo json_encode(["error" => "Failed to delete user"]); + } +} diff --git a/template.php b/template.php index 5537d50..627c064 100644 --- a/template.php +++ b/template.php @@ -1,6 +1,6 @@ - +