Implement queued email tasks
This commit is contained in:
parent
2c98d4e31e
commit
abdc808e4b
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.0.18",
|
||||
"version": "0.0.19",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.0.18",
|
||||
"version": "0.0.19",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -37,14 +37,15 @@ $conn = Database::connect($config["database"]["filename"]);
|
|||
$mediawiki = new Mediawiki($logger->withName("Mediawiki"));
|
||||
$user_manager = new UserManager($logger->withName("UserManager"), $conn);
|
||||
$tracking_manager = new TrackingManager($logger->withName("TrackingManager"), $mediawiki, $conn);
|
||||
$mailer = new Mailer($logger->withName("Mailer"), $config);
|
||||
$mailer = new Mailer($logger->withName("Mailer"), $config, $conn);
|
||||
|
||||
// Create db if it does not exist
|
||||
if (!$db_exists) {
|
||||
$logger->warning("Database does not exist. Creating new database at '" . $config["database"]["filename"] . "'.");
|
||||
$logger->warning("Database does not exist. Creating new database at '{$config["database"]["filename"]}'.");
|
||||
|
||||
$user_manager->install();
|
||||
$tracking_manager->install();
|
||||
$mailer->install();
|
||||
}
|
||||
|
||||
// Start session
|
||||
|
@ -205,7 +206,7 @@ if (isset($_POST["action"])) {
|
|||
break;
|
||||
default:
|
||||
$response = new Response(
|
||||
payload: ["target" => null, "message" => "Unknown POST action '" . $_POST["action"] . "'."],
|
||||
payload: ["target" => null, "message" => "Unknown POST action '{$_POST["action"]}'."],
|
||||
satisfied: false
|
||||
);
|
||||
break;
|
||||
|
@ -234,7 +235,7 @@ if (isset($_POST["action"])) {
|
|||
break;
|
||||
default:
|
||||
$response = new Response(
|
||||
payload: ["target" => null, "message" => "Unknown GET action '" . $_GET["action"] . "'."],
|
||||
payload: ["target" => null, "message" => "Unknown GET action '{$_GET["action"]}'."],
|
||||
satisfied: false
|
||||
);
|
||||
}
|
||||
|
@ -245,12 +246,17 @@ if (isset($_POST["action"])) {
|
|||
if (!hash_equals($config["admin"]["cli_secret"], $argv[2]))
|
||||
exit("Incorrect value for 'cli_secret'.");
|
||||
|
||||
if ($argv[1] === "update-all-trackings") {
|
||||
$logger->info("Updating all trackings.");
|
||||
$tracking_manager->update_trackings($tracking_manager->list_all_unique_person_names());
|
||||
exit("Successfully updated all trackings.");
|
||||
} else {
|
||||
exit("Unknown CLI action '" . $argv[1] . "'.");
|
||||
switch ($argv[1]) {
|
||||
case "update-all-trackings":
|
||||
$logger->info("Updating all trackings.");
|
||||
$tracking_manager->update_trackings($tracking_manager->list_all_unique_person_names());
|
||||
exit("Successfully updated all trackings.");
|
||||
case "process-email-queue":
|
||||
$logger->info("Processing email queue.");
|
||||
$mailer->process_queue();
|
||||
exit("Successfully processed email queue.");
|
||||
default:
|
||||
exit("Unknown CLI action '$argv[1]'.");
|
||||
}
|
||||
} else {
|
||||
// No action given, nothing done, so that's a success
|
||||
|
|
|
@ -18,7 +18,7 @@ class Database
|
|||
*/
|
||||
public static function connect(string $filename): PDO
|
||||
{
|
||||
return new PDO("sqlite:" . $filename, options: array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
||||
return new PDO("sqlite:$filename", options: array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
namespace php;
|
||||
|
||||
use Monolog\Logger;
|
||||
use PDO;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
||||
|
||||
/**
|
||||
* Sends mails.
|
||||
* Queues up mails and sends them when appropriate.
|
||||
*/
|
||||
class Mailer
|
||||
{
|
||||
|
@ -21,6 +22,10 @@ class Mailer
|
|||
* @var array The configuration to use for mailing.
|
||||
*/
|
||||
private array $config;
|
||||
/**
|
||||
* @var PDO The database connection to interact with.
|
||||
*/
|
||||
private PDO $conn;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -29,77 +34,188 @@ class Mailer
|
|||
* @param Logger $logger the logger to use for logging
|
||||
* @param array $config the configuration to use for mailing
|
||||
*/
|
||||
public function __construct(Logger $logger, array $config)
|
||||
public function __construct(Logger $logger, array $config, PDO $conn)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->config = $config;
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
|
||||
public function send_registration_email(string $email, string $token): Response
|
||||
/**
|
||||
* Populates the database with the necessary structures for emails.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
// TODO: Send mails asynchronously
|
||||
// TODO: Create cron job / task lists for emails
|
||||
// TODO: Tell user what to do
|
||||
return $this->send_email(
|
||||
[$email],
|
||||
"Created account for Death Notifier",
|
||||
"You created an account at Death Notifier. Please verify your email address by going to " .
|
||||
$this->config["server"]["base_path"] . "?action=verify-email&email=" . urlencode($email) . "&token=" . $token .
|
||||
" Until you verify your email address, you will not receive any notifications."
|
||||
);
|
||||
$this->conn->exec("CREATE TABLE email_tasks(user_uuid text not null,
|
||||
email_type text not null,
|
||||
PRIMARY KEY (user_uuid, email_type));");
|
||||
}
|
||||
|
||||
// TODO: Also send it to old address
|
||||
public function send_email_verification(string $old_email, string $new_email, string $token): Response
|
||||
/**
|
||||
* Queues an email to be sent for a newly registered user.
|
||||
*
|
||||
* @param string $uuid the UUID of the newly registered user
|
||||
* @return Response an empty satisfied response
|
||||
*/
|
||||
public function queue_registration(string $uuid): Response
|
||||
{
|
||||
return $this->send_email(
|
||||
[$new_email, $old_email],
|
||||
"Verify your email address",
|
||||
"Your email address for the Death Notifier has been changed. Please verify your email address by going " .
|
||||
"to " . $this->config["server"]["base_path"] . "?action=verify-email&email=" . urlencode($new_email) . "&token=" .
|
||||
$token . " Until you verify your email address, you will not receive any notifications."
|
||||
);
|
||||
}
|
||||
|
||||
private function send_email(array $destinations, string $subject, string $body): Response
|
||||
{
|
||||
$mail = new PHPMailer();
|
||||
$mail->IsSMTP();
|
||||
$mail->CharSet = "UTF-8";
|
||||
|
||||
$mail->SMTPDebug = SMTP::DEBUG_OFF;
|
||||
$mail->Host = $this->config["mail"]["host"];
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Port = $this->config["mail"]["port"];
|
||||
$mail->Username = $this->config["mail"]["username"];
|
||||
$mail->Password = $this->config["mail"]["password"];
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
try {
|
||||
$mail->setFrom($this->config["mail"]["username"], $this->config["mail"]["from_name"]);
|
||||
foreach ($destinations as $destination)
|
||||
$mail->addAddress($destination);
|
||||
} catch (Exception $exception) {
|
||||
$this->logger->warning("Failed to set 'from' and 'to' fields when sending email.", [$exception]);
|
||||
return new Response(
|
||||
payload: ["target" => null, "message" => "Unexpected error. Please try again later."],
|
||||
satisfied: false
|
||||
);
|
||||
}
|
||||
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
|
||||
try {
|
||||
$mail->send();
|
||||
} catch (Exception $exception) {
|
||||
$this->logger->warning("Failed to send test email.", [$exception]);
|
||||
return new Response(
|
||||
payload: ["target" => null, "message" => "Unexpected error. Please try again later."],
|
||||
satisfied: false
|
||||
);
|
||||
}
|
||||
$stmt = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (user_uuid, email_type)
|
||||
VALUES (:uuid, 'register');");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->execute();
|
||||
|
||||
return new Response(payload: null, satisfied: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the email subject and body to be sent to a newly registered user.
|
||||
*
|
||||
* @param string $email the email address of the newly registered user
|
||||
* @param string $token the email address verification token of the newly registered user
|
||||
* @return string[] the subject and body of the email
|
||||
*/
|
||||
private function create_register_email(string $email, string $token): array
|
||||
{
|
||||
$verify_path = "{$this->config["server"]["base_path"]}?action=verify-email&email=" . urlencode($email) . "&token=$token";
|
||||
|
||||
// TODO: Nicer message!
|
||||
// TODO: What if user did not create account?
|
||||
return [
|
||||
"Created account for Death Notifier",
|
||||
"An account has been created for $email for Death Notifier. " .
|
||||
"Until you verify your email address, you will not receive any notifications. " .
|
||||
"You can verify your email address by clicking the link below." .
|
||||
"\n\n" .
|
||||
"Verify: $verify_path"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues an email to be sent to a user who should verify their email address.
|
||||
*
|
||||
* @param string $uuid the UUID of the user who should verify their email address
|
||||
* @return Response an empty satisfied response
|
||||
*/
|
||||
public function queue_verification(string $uuid): Response
|
||||
{
|
||||
$stmt = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (user_uuid, email_type)
|
||||
VALUES (:uuid, 'verify');");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->execute();
|
||||
|
||||
return new Response(payload: null, satisfied: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the email subject and body to be sent to a user who should verify their email address.
|
||||
*
|
||||
* @param string $email the email address of the user
|
||||
* @param string $token the email address verification token of the user
|
||||
* @return string[] the subject and body of the email
|
||||
*/
|
||||
private function create_verify_email(string $email, string $token): array
|
||||
{
|
||||
$base_path = $this->config["server"]["base_path"];
|
||||
$verify_path = "$base_path?action=verify-email&email=" . urlencode($email) . "&token=$token";
|
||||
|
||||
return [
|
||||
"Verify your email address",
|
||||
"Your email address for the Death Notifier has been changed.
|
||||
Please verify your email address by going to $verify_path.
|
||||
Until you verify your email address, you will not receive any notifications."
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends all emails in the queue.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_queue(): void
|
||||
{
|
||||
// Open mailer
|
||||
$mailer = new PHPMailer();
|
||||
$mailer->IsSMTP();
|
||||
$mailer->CharSet = "UTF-8";
|
||||
|
||||
$mailer->SMTPAuth = true;
|
||||
$mailer->SMTPDebug = SMTP::DEBUG_OFF;
|
||||
$mailer->SMTPKeepAlive = true;
|
||||
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
$mailer->Host = $this->config["mail"]["host"];
|
||||
$mailer->Port = $this->config["mail"]["port"];
|
||||
$mailer->Username = $this->config["mail"]["username"];
|
||||
$mailer->Password = $this->config["mail"]["password"];
|
||||
try {
|
||||
$mailer->setFrom($this->config["mail"]["username"], $this->config["mail"]["from_name"]);
|
||||
} catch (Exception $exception) {
|
||||
$this->logger->error("Failed to set 'from' address while processing queue.", ["cause" => $exception]);
|
||||
$mailer->smtpClose();
|
||||
}
|
||||
|
||||
// Get queue
|
||||
$stmt = $this->conn->prepare("SELECT user_uuid, email_type FROM email_tasks;");
|
||||
$stmt->execute();
|
||||
$email_tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Process queue
|
||||
$stmt_user = $this->conn->prepare("SELECT email, email_is_verified, email_verification_token
|
||||
FROM users
|
||||
WHERE uuid=:uuid;");
|
||||
$stmt_user->bindParam(":uuid", $uuid);
|
||||
|
||||
$stmt_done = $this->conn->prepare("DELETE FROM email_tasks WHERE user_uuid=:uuid AND email_type=:type;");
|
||||
$stmt_done->bindParam(":uuid", $uuid);
|
||||
$stmt_done->bindParam(":type", $email_type);
|
||||
|
||||
foreach ($email_tasks as ["user_uuid" => $uuid, "email_type" => $email_type]) {
|
||||
$stmt_user->execute();
|
||||
$user = $stmt_user->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
switch ($email_type) {
|
||||
case "register":
|
||||
[$mailer->Subject, $mailer->Body] =
|
||||
$this->create_register_email($user["email"], $user["email_verification_token"]);
|
||||
|
||||
try {
|
||||
$mailer->addAddress($user["email"]);
|
||||
$mailer->send();
|
||||
$stmt_done->execute();
|
||||
} catch (Exception $exception) {
|
||||
$this->logger->error(
|
||||
"Failed to send register mail.",
|
||||
["cause" => $exception, "recipient" => $user["email"]]
|
||||
);
|
||||
$mailer->getSMTPInstance()->reset();
|
||||
}
|
||||
break;
|
||||
case "verify":
|
||||
if ($user["email_verified"]) {
|
||||
$stmt_done->execute();
|
||||
break;
|
||||
}
|
||||
|
||||
[$mailer->Subject, $mailer->Body] =
|
||||
$this->create_verify_email($user["email"], $user["email_verification_token"]);
|
||||
|
||||
try {
|
||||
// TODO: Also send to old email address (by storing old email in `users` table?)
|
||||
$mailer->addAddress($user["email"]);
|
||||
$mailer->send();
|
||||
$stmt_done->execute();
|
||||
} catch (Exception $exception) {
|
||||
$this->logger->error(
|
||||
"Failed to send verify mail.",
|
||||
["cause" => $exception, "recipient" => $user["email"]]
|
||||
);
|
||||
$mailer->getSMTPInstance()->reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
$mailer->clearAddresses();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ class UserManager
|
|||
|
||||
// Respond
|
||||
$this->conn->commit();
|
||||
$mailer->send_registration_email($email, $email_verification_token);
|
||||
$mailer->queue_registration($uuid);
|
||||
return new Response(payload: null, satisfied: true);
|
||||
}
|
||||
|
||||
|
@ -297,7 +297,7 @@ class UserManager
|
|||
|
||||
// Respond
|
||||
$this->conn->commit();
|
||||
$mailer->send_email_verification($email_old, $email, $email_verification_token);
|
||||
$mailer->queue_verification($uuid);
|
||||
return new Response(payload: null, satisfied: true);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue