Reduce cron job overheads, add some extra logging
This commit is contained in:
parent
44b62d32d0
commit
3626175bac
|
@ -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",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = " ";
|
||||
spacerCell.appendChild(spacer);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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."],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue