Reduce cron job overheads, add some extra logging

This commit is contained in:
Florine W. Dekker 2022-12-12 17:37:07 +01:00
parent 44b62d32d0
commit 3626175bac
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
19 changed files with 86 additions and 48 deletions

View File

@ -1,7 +1,7 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "0.19.2", "_comment_version": "Also update version in `package.json`!",
"version": "0.19.3", "_comment_version": "Also update version in `package.json`!",
"type": "project",
"license": "MIT",
"homepage": "https://git.fwdekker.com/tools/death-notifier",

BIN
composer.lock generated

Binary file not shown.

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "death-notifier",
"version": "0.19.2", "_comment_version": "Also update version in `composer.json`!",
"version": "0.19.3", "_comment_version": "Also update version in `composer.json`!",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -47,6 +47,11 @@ a.red-link {
flex-grow: 1;
}
#add-tracking-name-hint {
/*noinspection CssUnresolvedCustomProperty*/
margin-top: calc(var(--spacing) * -.75);
}
@media (min-width: 992px) {
#filter-trackings-query {
float: right;

View File

@ -226,7 +226,6 @@
<small id="add-tracking-name-hint" data-hint-for="add-tracking-name"></small>
</form>
<form id="filter-trackings-form" novalidate>
<!-- TODO: Find intuitive way of distinguishing search and add -->
<!--suppress HtmlFormInputWithoutLabel -->
<input id="filter-trackings-query" type="search" name="query" placeholder="Search" />
</form>
@ -321,7 +320,7 @@
</article>
<label for="update-password-password-old">Old password</label>
<input id="update-password-password-old" type="password" name="password"
<input id="update-password-password-old" type="password" name="password_old"
autocomplete="current-password" />
<small id="update-password-password-old-hint"
data-hint-for="update-password-password-old"></small>
@ -333,7 +332,7 @@
<br /><br />
<label for="update-password-password-new">New password</label>
<input id="update-password-password-new" type="password" name="password"
<input id="update-password-password-new" type="password" name="password_new"
autocomplete="new-password" />
<small id="update-password-password-new-hint" data-hint-for="update-password-password-new"
data-hint="Use at least 8 characters."></small>

View File

@ -38,6 +38,7 @@ function createPlaceholderRow(text: string): HTMLTableRowElement {
const spacerCell = document.createElement("td");
const spacer = document.createElement("button");
spacer.ariaHidden = "true";
spacer.tabIndex = -1;
spacer.classList.add("invisible");
spacer.innerText = "&nbsp;";
spacerCell.appendChild(spacer);

View File

@ -45,9 +45,10 @@ class EmulateCronAction extends Action
*/
public function handle(array $inputs): never
{
$logger = LoggerUtil::with_name($this::class);
// @phpstan-ignore-next-line
while (true) {
// TODO: Log last cron task in database, to make it really clear when it last happened
$logger->info("Emulating cron job.");
foreach ($this->actions as $action)
$action->handle($inputs);
print("Done.\n");

View File

@ -57,24 +57,32 @@ class EmailQueue
/**
* Adds an {@see Email} to the queue.
* Adds {@see Email Emails} to the queue.
*
* {@see email} is added to the database, with its primary key determined by {@see Email::$type_key} and
* {@see Email::$recipient}. If an {@see Email} with this key is already in this queue, it is replaced with
* {@see $email}.
* {@see emails} are added to the database, with the primary keys determined by {@see Email::$type_key} and
* {@see Email::$recipient}. If an {@see Email} with this key is already in the queue, it is replaced with
* {@see $emails}.
*
* @param Email $email the email to queue
* @param array<Email> $emails the emails to queue
* @return void
*/
public function queue_email(Email $email): void
public function queue_emails(array $emails): void
{
$stmt = $this->database->conn->prepare("INSERT OR REPLACE INTO email_tasks (recipient, type_key, subject, body)
VALUES (:recipient, :type_key, :subject, :body);");
$stmt->bindValue(":recipient", $email->recipient);
$stmt->bindValue(":type_key", $email->type_key);
$stmt->bindValue(":subject", $email->get_subject());
$stmt->bindValue(":body", $email->get_body());
$stmt->execute();
$stmt->bindParam(":recipient", $recipient);
$stmt->bindParam(":type_key", $type_key);
$stmt->bindParam(":subject", $subject);
$stmt->bindParam(":body", $body);
foreach ($emails as $email) {
$recipient = $email->recipient;
$type_key = $email->type_key;
$subject = $email->get_subject();
$body = $email->get_body();
$stmt->execute();
}
}
/**

View File

@ -75,7 +75,7 @@ class AddTrackingAction extends Action
[$normalized_name, $status] = $this->get_and_validate_page_info(strval($inputs["person_name"]));
$this->tracking_list->transaction(function () use ($normalized_name, $status) {
if ($this->tracking_list->has_tracking($_SESSION["uuid"], $normalized_name))
throw new InvalidValueException("You are already tracking <b>$normalized_name</b>.");
throw new InvalidValueException("You are already tracking <b>$normalized_name</b>.", "person_name");
$this->tracking_list->add_tracking($_SESSION["uuid"], $normalized_name, $status);
});

View File

@ -169,10 +169,11 @@ class TrackingList
/**
* Returns the email addresses of all users who should be notified of events relating to the given person.
*
* @param string $person_name the person to receive subscribed email addresses for
* @return string[] the email addresses of all users who should be notified of events relating to the given person
* @param array<string> $person_names the persons to receive subscribed email addresses for
* @return array<string, string[]> a map of each {@see $person_names} to the email addresses of all users tracking
* that person
*/
public function list_trackers(string $person_name): array
public function list_trackers(array $person_names): array
{
$stmt = $this->database->conn->prepare("SELECT users.email
FROM users
@ -182,8 +183,14 @@ class TrackingList
AND users.email_verification_token IS NULL
AND users.email_notifications_enabled=1;");
$stmt->bindParam(":person_name", $person_name);
$stmt->execute();
return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), "email");
$trackers = [];
foreach ($person_names as $person_name) {
$stmt->execute();
$trackers[$person_name] = array_column($stmt->fetchAll(PDO::FETCH_ASSOC), "email");
}
return $trackers;
}
@ -303,9 +310,8 @@ class TrackingList
/**
* Updates peoples' statuses.
*
* @param array<string, PersonStatus|null> $statuses the current statuses of
* people
* @return array<string, string> a mapping of articles that were actually changes to the new status
* @param array<string, PersonStatus|null> $statuses the current statuses of people
* @return array<string, PersonStatus> a mapping of articles that were actually changes to the new status
*/
public function update_statuses(array $statuses): array
{
@ -326,7 +332,7 @@ class TrackingList
$stmt->execute();
if (sizeof($stmt->fetchAll(PDO::FETCH_ASSOC)) > 0)
$status_changes[$person_name] = $person_status_string;
$status_changes[$person_name] = $person_status;
}
});

View File

@ -12,6 +12,7 @@ use com\fwdekker\deathnotifier\validation\EqualsCliPasswordRule;
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
use com\fwdekker\deathnotifier\validation\InvalidValueException;
use com\fwdekker\deathnotifier\validation\RuleSet;
use com\fwdekker\deathnotifier\wikipedia\PersonStatus;
use com\fwdekker\deathnotifier\wikipedia\Wikipedia;
use com\fwdekker\deathnotifier\wikipedia\WikipediaException;
use Monolog\Logger;
@ -101,23 +102,38 @@ class UpdateTrackingsAction extends Action
);
});
// Send mails
// Send mails, log events
// TODO: Restrict number of notifications to 1 per hour (excluding "oops we're not sure" message)
// TODO: Reuse `stmt`s for listing trackers and queueing emails to reduce overheads
// TODO: Detect renaming vandalism
// TODO: Log noteworthy events in a separate log, including renames, deletions, alive again
foreach ($new_deletions as $new_deletion)
foreach ($this->tracking_list->list_trackers($new_deletion) as $user_email)
$this->mailer->queue_email(new NotifyArticleDeletedEmail($user_email, $new_deletion));
$logger = LoggerUtil::with_name($this::class);
$emails = [];
foreach ($new_undeletions as $undeletion)
foreach ($this->tracking_list->list_trackers($undeletion) as $user_email)
$this->mailer->queue_email(new NotifyArticleUndeletedEmail($user_email, $undeletion));
$person_names = $new_deletions + $new_undeletions + array_keys($new_status_changes);
$trackers = $this->tracking_list->list_trackers($person_names);
foreach ($new_status_changes as $person_name => $person_status)
foreach ($this->tracking_list->list_trackers($person_name) as $user_email)
$this->mailer->queue_email(new NotifyStatusChangedEmail($user_email, $person_name, $person_status));
foreach ($new_deletions as $new_deletion) {
$logger->warning("Deleted article $new_deletion.");
foreach ($trackers[$new_deletion] as $user_email)
$emails[] = new NotifyArticleDeletedEmail($user_email, $new_deletion);
}
foreach ($new_undeletions as $new_undeletion) {
$logger->warning("Undeleted article $new_undeletion.");
foreach ($trackers[$new_undeletion] as $user_email)
$emails[] = new NotifyArticleUndeletedEmail($user_email, $new_undeletion);
}
foreach ($new_status_changes as $person_name => $person_status) {
if ($person_status === PersonStatus::Alive)
$logger->warning("Person $person_name is now alive again.");
foreach ($trackers[$person_name] as $user_email)
$emails[] = new NotifyStatusChangedEmail($user_email, $person_name, $person_status->value);
}
$this->mailer->queue_emails($emails);
return null;
}
}

View File

@ -79,8 +79,10 @@ class ChangeEmailAction extends Action
throw new InvalidValueException("That email address is already in use by someone else.", "email");
$token = $this->user_list->set_email($_SESSION["uuid"], $inputs["email"]);
$this->email_queue->queue_email(new ChangeEmailFromEmail($user_data["email"], $inputs["email"]));
$this->email_queue->queue_email(new ChangeEmailToEmail($inputs["email"], $user_data["email"], $token));
$this->email_queue->queue_emails([
new ChangeEmailFromEmail($user_data["email"], $inputs["email"]),
new ChangeEmailToEmail($inputs["email"], $user_data["email"], $token)
]);
});
return null;

View File

@ -77,7 +77,7 @@ class ChangePasswordAction extends Action
throw new InvalidValueException("Incorrect old password.", "password_old");
$this->user_list->set_password($_SESSION["uuid"], $inputs["password_new"]);
$this->email_queue->queue_email(new ChangePasswordEmail($user_data["email"]));
$this->email_queue->queue_emails([new ChangePasswordEmail($user_data["email"])]);
});
return null;

View File

@ -71,7 +71,7 @@ class RegisterAction extends Action
throw new InvalidValueException("That email address already in use.", "email");
$token = $this->user_list->add_user($inputs["email"], $inputs["password"]);
$this->email_queue->queue_email(new RegisterEmail($inputs["email"], $token));
$this->email_queue->queue_emails([new RegisterEmail($inputs["email"], $token)]);
});
return null;

View File

@ -82,7 +82,7 @@ class ResendVerifyEmailAction extends Action
}
$token = $this->user_list->set_email($_SESSION["uuid"], $user_data["email"]);
$this->email_queue->queue_email(new ResendVerifyEmailEmail($user_data["email"], $token));
$this->email_queue->queue_emails([new ResendVerifyEmailEmail($user_data["email"], $token)]);
});
return null;

View File

@ -81,7 +81,7 @@ class ResetPasswordAction extends Action
throw new IllegalStateError("User data is `null` despite previous validation.");
$this->user_list->set_password($user_data["uuid"], $inputs["password"]);
$this->email_queue->queue_email(new ResetPasswordEmail($inputs["email"]));
$this->email_queue->queue_emails([new ResetPasswordEmail($inputs["email"])]);
});
return null;

View File

@ -80,7 +80,7 @@ class SendPasswordResetAction extends Action
}
$token = $this->user_list->register_password_reset($inputs["email"]);
$this->email_queue->queue_email(new SendPasswordResetEmail($inputs["email"], $token));
$this->email_queue->queue_emails([new SendPasswordResetEmail($inputs["email"], $token)]);
});
return null;

View File

@ -13,8 +13,8 @@ class IsValidCsrfTokenRuleTest extends RuleTest
$type = InvalidTypeException::class;
return [
"exception if input is not set" => [new IsValidCsrfTokenRule(), null, $type, "Required input 'key' not set."],
"exception if input is not a string" => [new IsValidCsrfTokenRule(), 170, $type, "Input 'key' should be string, but is integer."],
"exception if input is not set" => [new IsValidCsrfTokenRule(), null, $type, "Missing CSRF token."],
"exception if input is not a string" => [new IsValidCsrfTokenRule(), 170, $type, "CSRF token should be string, but is integer."],
];
}