Many Tings

This commit is contained in:
Hickmeister
2025-01-16 23:13:31 +00:00
parent 053662c2e8
commit 8410a50f26
11 changed files with 772 additions and 2 deletions

View File

@@ -2,6 +2,8 @@
"require": {
"php-mqtt/client": "^2.2",
"fabpot/goutte": "^4.0",
"symfony/polyfill-ctype": "^1.31"
"symfony/polyfill-ctype": "^1.31",
"phpmailer/phpmailer": "^6.9",
"twig/twig": "^3.18"
}
}

239
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a6dcfb9667ce99508becf6cd0f9310b9",
"content-hash": "8ffb6b03780af1286dc4d1cd3d8f0445",
"packages": [
{
"name": "fabpot/goutte",
@@ -250,6 +250,87 @@
},
"time": "2024-11-24T20:54:32+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.9.3",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"ext-hash": "*",
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.7.2",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPMailer\\PHPMailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-only"
],
"authors": [
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"name": "Andy Prevost",
"email": "codeworxtech@users.sourceforge.net"
},
{
"name": "Brent R. Matzelle"
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
},
"funding": [
{
"url": "https://github.com/Synchro",
"type": "github"
}
],
"time": "2024-11-24T18:04:13+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
@@ -1199,6 +1280,82 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.5.1",
@@ -1281,6 +1438,86 @@
}
],
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "twig/twig",
"version": "v3.18.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
},
"type": "library",
"autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.18.0"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2024-12-29T10:51:50+00:00"
}
],
"packages-dev": [],

View File

@@ -0,0 +1,162 @@
<?php
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Source;
use Twig\Template;
use Twig\TemplateWrapper;
/* filament_price_summary.html.twig */
class __TwigTemplate_fb2b23b970fb7474e15dec31809a1a27 extends Template
{
private Source $source;
/**
* @var array<string, Template>
*/
private array $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->parent = false;
$this->blocks = [
];
}
protected function doDisplay(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
// line 1
yield "<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">
<title>Filament Price Change Summary</title>
<style>
.table-container {
overflow-x: auto;
}
.price-drop {
color: #28a745; /* Green for price drop */
font-weight: bold;
}
.price-rise {
color: #dc3545; /* Red for price rise */
font-weight: bold;
}
</style>
</head>
<body>
<div class=\"container py-5\">
<h1 class=\"mb-4\">Filament Price Change Summary</h1>
<p class=\"lead\">Here's the latest update on filament prices from your tracker:</p>
<div class=\"table-container\">
<table class=\"table table-striped table-bordered\">
<thead class=\"thead-dark\">
<tr>
<th>Filament Name</th>
<th>Brand</th>
<th>New Price</th>
<th>Old Price</th>
<th>Change</th>
<th>Amazon Link</th>
</tr>
</thead>
<tbody>
";
// line 39
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(($context["filaments"] ?? null));
foreach ($context['_seq'] as $context["_key"] => $context["filament"]) {
// line 40
yield " <tr>
<td>";
// line 41
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "filamentName", [], "any", false, false, false, 41), "html", null, true);
yield "</td>
<td>";
// line 42
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "brand", [], "any", false, false, false, 42), "html", null, true);
yield "</td>
<td>";
// line 43
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "newPrice", [], "any", false, false, false, 43), "html", null, true);
yield "</td>
<td>";
// line 44
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "oldPrice", [], "any", false, false, false, 44), "html", null, true);
yield "</td>
<td class=\"price-drop\">";
// line 45
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "priceChange", [], "any", false, false, false, 45), "html", null, true);
yield "</td>
<td><a href=\"";
// line 46
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "amazonUrl", [], "any", false, false, false, 46), "html", null, true);
yield "\" target=\"_blank\">View on Amazon</a></td>
</tr>
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['filament'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 49
yield " </tbody>
</table>
</div>
<p class=\"mt-4\">
Visit your <a href=\"";
// line 53
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["dashboardUrl"] ?? null), "html", null, true);
yield "\" target=\"_blank\">dashboard</a> for more details.
</p>
</div>
</body>
</html>
";
yield from [];
}
/**
* @codeCoverageIgnore
*/
public function getTemplateName(): string
{
return "filament_price_summary.html.twig";
}
/**
* @codeCoverageIgnore
*/
public function isTraitable(): bool
{
return false;
}
/**
* @codeCoverageIgnore
*/
public function getDebugInfo(): array
{
return array ( 124 => 53, 118 => 49, 109 => 46, 105 => 45, 101 => 44, 97 => 43, 93 => 42, 89 => 41, 86 => 40, 82 => 39, 42 => 1,);
}
public function getSourceContext(): Source
{
return new Source("", "filament_price_summary.html.twig", "/mnt/www-live/TechOdyssey_Designs_Dashboard/src/emailService/templates/filament_price_summary.html.twig");
}
}

View File

@@ -0,0 +1,9 @@
body {
font-family: Arial, sans-serif;
background-color: #f9f9f9;
color: #333;
}
.alert {
border-radius: 10px;
}

View File

@@ -0,0 +1,54 @@
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require_once '../envLoader.php';
loadEnv('../../.env');
require '../../vendor/autoload.php';
function sendEmail($to, $subject, $template, $variables = []) {
$mail = new PHPMailer(true);
try {
// Load the template
$templatePath = __DIR__ . "/templates/$template.html";
if (!file_exists($templatePath)) {
throw new Exception("Template not found: $template");
}
$body = file_get_contents($templatePath);
// Replace variables
foreach ($variables as $key => $value) {
print_r($key);
$body = str_replace("{{" . $key . "}}", strval($value), $body);
}
// Add environment-based dynamic replacements
$body = str_replace("{{website_url}}", strval($_ENV['WEBSITE_URL']), $body);
$body = str_replace("{{business_name}}", strval($_ENV['BUSINESS_NAME']), $body);
// SMTP Configuration
$mail->isSMTP();
$mail->Host = $_ENV['SMTP_HOST'];
$mail->SMTPAuth = true;
$mail->Username = $_ENV['SMTP_USER'];
$mail->Password = $_ENV['SMTP_PASS'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = $_ENV['SMTP_PORT'];
// Email Settings
$mail->setFrom($_ENV['SMTP_USER'], $_ENV['BUSINESS_NAME']);
$mail->addAddress($to);
$mail->Subject = $subject;
$mail->isHTML(true);
$mail->Body = $body;
$mail->send();
echo "Email sent successfully to $to";
} catch (Exception $e) {
echo "Error: {$mail->ErrorInfo}";
}
}
?>

View File

@@ -0,0 +1,60 @@
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
require_once '../envLoader.php';
loadEnv('../../.env');
require '../../vendor/autoload.php';
function sendEmail($to, $subject, $template, $variables = []) {
$mail = new PHPMailer(true);
try {
// Set up Twig
$loader = new FilesystemLoader(__DIR__ . '/templates');
$twig = new Environment($loader, [
'cache' => __DIR__ . '/cache', // Optional: Enable Twig cache
'auto_reload' => true,
]);
// Render the Twig template
$body = $twig->render($template . '.html.twig', $variables);
// SMTP Configuration
$mail->isSMTP();
$mail->Host = $_ENV['SMTP_HOST'];
$mail->SMTPAuth = true;
$mail->Username = $_ENV['SMTP_USER'];
$mail->Password = $_ENV['SMTP_PASS'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = $_ENV['SMTP_PORT'];
// Email Charset and Settings
$mail->CharSet = 'UTF-8'; // Ensure correct encoding
$mail->setFrom($_ENV['SMTP_USER'], $_ENV['BUSINESS_NAME']);
// Handle multiple recipients
if (is_array($to)) {
foreach ($to as $recipient) {
$mail->addAddress($recipient);
}
} else {
$mail->addAddress($to); // Single recipient
}
$mail->Subject = $subject;
$mail->isHTML(true);
$mail->Body = $body;
$mail->send();
foreach ($to as $recipient) {
echo "Email sent successfully to $recipient\n";
}
} catch (Exception $e) {
echo "Error: {$mail->ErrorInfo}";
}
}
?>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Error Alert</title>
</head>
<body>
<div class="container py-5">
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Error!</h4>
<p>{{message}}</p>
<hr>
<p class="mb-0">For more details, visit our <a href="https://yourwebsite.com">dashboard</a>.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Info Alert</title>
</head>
<body>
<div class="container py-5">
<div class="alert alert-info" role="alert">
<h4 class="alert-heading">Information</h4>
<p>{{message}}</p>
<hr>
<p class="mb-0">For more details, visit our <a href="https://yourwebsite.com">dashboard</a>.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Success Alert</title>
</head>
<body>
<div class="container py-5">
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">Success!</h4>
<p>{{message}}</p>
<hr>
<p class="mb-0">For more details, visit our <a href="https://yourwebsite.com">dashboard</a>.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Filament Price Change Summary</title>
<style>
.table-container {
overflow-x: auto;
}
.price-drop {
color: #28a745; /* Green for price drop */
font-weight: bold;
}
.price-rise {
color: #dc3545; /* Red for price rise */
font-weight: bold;
}
</style>
</head>
<body>
<div class="container py-5">
<h1 class="mb-4">Filament Price Change Summary</h1>
<p class="lead">Here's the latest update on filament prices from your tracker:</p>
<div class="table-container">
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th>Filament Name</th>
<th>Brand</th>
<th>New Price</th>
<th>Old Price</th>
<th>Change</th>
<th>Amazon Link</th>
</tr>
</thead>
<tbody>
{% for filament in filaments %}
<tr>
<td>{{ filament.filamentName }}</td>
<td>{{ filament.brand }}</td>
<td>{{ filament.newPrice }}</td>
<td>{{ filament.oldPrice }}</td>
<td class="price-drop">{{ filament.priceChange }}</td>
<td><a href="{{ filament.amazonUrl }}" target="_blank">View on Amazon</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p class="mt-4">
Visit your <a href="{{ dashboardUrl }}" target="_blank">dashboard</a> for more details.
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,132 @@
<?php
require '../../vendor/autoload.php';
require '../db.php';
require_once '../envLoader.php';
loadEnv('../../.env');
require_once '../emailService/sendPriceDropEmailService.php';
// Fetch list of all filaments being tracked
function getFilaments() {
global $pdo;
try {
$stmt = $pdo->query("SELECT id, filamentName, brand, amazonUrl FROM filamentTracker");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "Error fetching filaments: " . $e->getMessage();
return [];
}
}
// Fetch the latest prices for each filament
function getCurrentPrices() {
global $pdo;
try {
$stmt = $pdo->query("
SELECT fp.filamentId, fp.price, fp.recordedAt
FROM filamentPriceHistory fp
INNER JOIN (
SELECT filamentId, MAX(recordedAt) AS latestRecordedAt
FROM filamentPriceHistory
GROUP BY filamentId
) latest ON fp.filamentId = latest.filamentId AND fp.recordedAt = latest.latestRecordedAt
");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "Error fetching current prices: " . $e->getMessage();
return [];
}
}
// Fetch prices from the last 24 hours for each filament
function getPriceHistoryForLast24Hours() {
global $pdo;
try {
$stmt = $pdo->query("
SELECT filamentId, price, recordedAt
FROM filamentPriceHistory
WHERE recordedAt >= NOW() - INTERVAL 1 DAY
ORDER BY filamentId, recordedAt DESC
");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "Error fetching price history for the last 24 hours: " . $e->getMessage();
return [];
}
}
// Compare prices and find drops
function checkPriceDropsAndNotify() {
$filaments = getFilaments();
$currentPrices = getCurrentPrices();
$priceHistory = getPriceHistoryForLast24Hours();
// Index prices by filamentId for quick lookup
$currentPricesMap = [];
foreach ($currentPrices as $current) {
$currentPricesMap[$current['filamentId']] = $current['price'];
}
// Group price history by filamentId
$priceHistoryMap = [];
foreach ($priceHistory as $history) {
$priceHistoryMap[$history['filamentId']][] = $history['price'];
}
$priceDrops = [];
foreach ($filaments as $filament) {
$filamentId = $filament['id'];
$currentPrice = $currentPricesMap[$filamentId] ?? null;
if ($currentPrice !== null && isset($priceHistoryMap[$filamentId])) {
foreach ($priceHistoryMap[$filamentId] as $oldPrice) {
if ($currentPrice < (float)$oldPrice) {
$priceDrops[] = [
'filamentName' => $filament['filamentName'],
'brand' => $filament['brand'] ?: 'Unknown',
'newPrice' => "£" . number_format($currentPrice, 2),
'oldPrice' => "£" . number_format((float)$oldPrice, 2),
'priceChange' => "" . number_format((float)$oldPrice - $currentPrice, 2),
'amazonUrl' => $filament['amazonUrl']
];
break; // Stop checking once a price drop is detected
}
}
}
}
function getRecipients() {
global $pdo;
try {
$stmt = $pdo->query("SELECT `email` FROM `users` WHERE `alertEmails` = 1");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
} catch (PDOException $e) {
echo "Error fetching recipients: " . $e->getMessage();
return [];
}
}
// Send email if there are price drops
if (!empty($priceDrops)) {
$recipient = getRecipients();
$subject = 'Filament Price Change Summary';
$data = [
'filaments' => $priceDrops,
'dashboardUrl' => $_ENV['WEBSITE_URL'] . '/dashboard'
];
sendEmail($recipient, $subject, 'filament_price_summary', $data);
echo "Email sent for the following price drops:\n";
foreach ($priceDrops as $drop) {
echo "- {$drop['filamentName']}: Price dropped from {$drop['oldPrice']} to {$drop['newPrice']}\n";
}
} else {
echo "No price drops detected.\n";
}
}
checkPriceDropsAndNotify();
?>