Clarify several error messages and signatures
This commit is contained in:
parent
fbce900475
commit
c49cd7184f
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.17.3", "_comment_version": "Also update version in `package.json`!",
|
||||
"version": "0.17.4", "_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.17.3", "_comment_version": "Also update version in `composer.json`!",
|
||||
"version": "0.17.4", "_comment_version": "Also update version in `composer.json`!",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -5,7 +5,6 @@ use com\fwdekker\deathnotifier\ActionMethod;
|
|||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\Database;
|
||||
use com\fwdekker\deathnotifier\EmulateCronAction;
|
||||
use com\fwdekker\deathnotifier\IllegalArgumentError;
|
||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
||||
use com\fwdekker\deathnotifier\mailer\ProcessEmailQueueAction;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use InvalidArgumentException;
|
||||
use Monolog\Logger;
|
||||
|
||||
|
||||
|
|
|
@ -68,12 +68,12 @@ class Util
|
|||
*
|
||||
* For example, if {@see $timestamp} was 5 minutes ago, and {@see $interval} is 7, then this function returns 2.
|
||||
*
|
||||
* @param string $timestamp the timestamp at which some event occurred
|
||||
* @param int $timestamp the timestamp at which some event occurred
|
||||
* @param int $interval the number of minutes to measure against
|
||||
* @return int the number of minutes until {@see $timestamp} was {@see $interval} minutes ago
|
||||
*/
|
||||
static function minutes_until_interval_elapsed(string $timestamp, int $interval): int
|
||||
static function minutes_until_interval_elapsed(int $timestamp, int $interval): int
|
||||
{
|
||||
return $interval - ((time() - intval($timestamp)) / 60);
|
||||
return $interval - ((time() - $timestamp) / 60);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace com\fwdekker\deathnotifier\tracking;
|
|||
use com\fwdekker\deathnotifier\Action;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\IsNotBlankRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsStringRule;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace com\fwdekker\deathnotifier\tracking;
|
||||
|
||||
use com\fwdekker\deathnotifier\Database;
|
||||
use com\fwdekker\deathnotifier\wikipedia\ArticleType;
|
||||
use com\fwdekker\deathnotifier\wikipedia\PersonStatus;
|
||||
use PDO;
|
||||
|
||||
|
@ -256,17 +255,16 @@ class TrackingList
|
|||
$new_deletions = [];
|
||||
|
||||
$this->transaction(function () use ($deletions, &$new_deletions) {
|
||||
$delete = $this->database->conn->prepare("UPDATE people
|
||||
SET is_deleted=1
|
||||
WHERE name=:name AND is_deleted<>1
|
||||
RETURNING name;");
|
||||
$delete->bindParam(":name", $deleted_name);
|
||||
$stmt = $this->database->conn->prepare("UPDATE people
|
||||
SET is_deleted=1
|
||||
WHERE name=:name AND is_deleted<>1
|
||||
RETURNING name;");
|
||||
$stmt->bindParam(":name", $deleted_name);
|
||||
|
||||
foreach ($deletions as $deleted_name) {
|
||||
$delete->execute();
|
||||
$newly_deleted = sizeof($delete->fetchAll(PDO::FETCH_ASSOC)) > 0;
|
||||
$stmt->execute();
|
||||
|
||||
if ($newly_deleted)
|
||||
if (sizeof($stmt->fetchAll(PDO::FETCH_ASSOC)) > 0)
|
||||
$new_deletions[] = $deleted_name;
|
||||
}
|
||||
});
|
||||
|
@ -275,51 +273,63 @@ class TrackingList
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates peoples' statuses.
|
||||
* Marks people as undeleted in the database.
|
||||
*
|
||||
* @param array<string, array{"type": ArticleType, "status": PersonStatus|null}> $statuses the current statuses of
|
||||
* people
|
||||
* @return array{string[], array<string, string>} the list of articles that were actually undeleted, and a mapping
|
||||
* of articles that were actually changes to the new status
|
||||
* @param string[] $undeletions list of names of people to mark as undeleted in the database
|
||||
* @return string[] subset of {@see $undeletions} containing the names of people that were newly marked as undeleted
|
||||
*/
|
||||
public function update_statuses(array $statuses): array
|
||||
public function undelete_persons(array $undeletions): array
|
||||
{
|
||||
$undeletions = [];
|
||||
$status_changes = [];
|
||||
$new_undeletions = [];
|
||||
|
||||
$this->transaction(function () use ($statuses, &$undeletions, &$status_changes) {
|
||||
$conn = $this->database->conn;
|
||||
$this->transaction(function () use ($undeletions, &$new_undeletions) {
|
||||
$stmt = $this->database->conn->prepare("UPDATE people
|
||||
SET is_deleted=0
|
||||
WHERE name=:name AND is_deleted<>0
|
||||
RETURNING name;");
|
||||
$stmt->bindParam(":name", $undeleted_name);
|
||||
|
||||
// Query to mark person as no longer deleted, returning `name` to determine whether something changed
|
||||
// TODO: Split this into two methods, one for `undelete`, one for `update_statuses`?
|
||||
$undelete = $conn->prepare("UPDATE people
|
||||
SET is_deleted=0
|
||||
WHERE name=:name AND is_deleted<>0
|
||||
RETURNING name;");
|
||||
$undelete->bindParam(":name", $person_name);
|
||||
foreach ($undeletions as $undeleted_name) {
|
||||
$stmt->execute();
|
||||
|
||||
// Query to update status, returning `name` to determine whether something changed
|
||||
$set_status = $conn->prepare("UPDATE people
|
||||
SET status=:status
|
||||
WHERE name=:name AND status<>:status
|
||||
RETURNING name;");
|
||||
$set_status->bindParam(":status", $person_status_string);
|
||||
$set_status->bindParam(":name", $person_name);
|
||||
|
||||
foreach ($statuses as $person_name => $person_info) {
|
||||
if ($person_info["status"] === null) continue;
|
||||
$person_status_string = $person_info["status"]->value;
|
||||
|
||||
$undelete->execute();
|
||||
$undeleted = sizeof($undelete->fetchAll(PDO::FETCH_ASSOC)) > 0;
|
||||
if ($undeleted) $undeletions[] = $person_name;
|
||||
|
||||
$set_status->execute();
|
||||
$status_changed = sizeof($set_status->fetchAll(PDO::FETCH_ASSOC)) > 0;
|
||||
if ($status_changed) $status_changes[$person_name] = $person_status_string;
|
||||
if (sizeof($stmt->fetchAll(PDO::FETCH_ASSOC)) > 0)
|
||||
$new_undeletions[] = $undeleted_name;
|
||||
}
|
||||
});
|
||||
|
||||
return [$undeletions, $status_changes];
|
||||
return $new_undeletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function update_statuses(array $statuses): array
|
||||
{
|
||||
$status_changes = [];
|
||||
|
||||
$this->transaction(function () use ($statuses, &$status_changes) {
|
||||
$stmt = $this->database->conn->prepare("UPDATE people
|
||||
SET status=:status
|
||||
WHERE name=:name AND status<>:status
|
||||
RETURNING name;");
|
||||
$stmt->bindParam(":status", $person_status_string);
|
||||
$stmt->bindParam(":name", $person_name);
|
||||
|
||||
foreach ($statuses as $person_name => $person_status) {
|
||||
if ($person_status === null) continue;
|
||||
|
||||
$person_status_string = $person_status->value;
|
||||
$stmt->execute();
|
||||
|
||||
if (sizeof($stmt->fetchAll(PDO::FETCH_ASSOC)) > 0)
|
||||
$status_changes[$person_name] = $person_status_string;
|
||||
}
|
||||
});
|
||||
|
||||
return $status_changes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,9 +74,9 @@ class UpdateTrackingsAction extends Action
|
|||
|
||||
// Process changes
|
||||
$new_deletions = [];
|
||||
$undeletions = [];
|
||||
$status_changes = [];
|
||||
$this->tracking_list->transaction(function () use (&$new_deletions, &$undeletions, &$status_changes) {
|
||||
$new_undeletions = [];
|
||||
$new_status_changes = [];
|
||||
$this->tracking_list->transaction(function () use (&$new_deletions, &$new_undeletions, &$new_status_changes) {
|
||||
$names = $this->tracking_list->list_all_unique_person_names();
|
||||
if (empty($names))
|
||||
return;
|
||||
|
@ -90,7 +90,13 @@ class UpdateTrackingsAction extends Action
|
|||
|
||||
$this->tracking_list->rename_persons($people_statuses->redirects);
|
||||
$new_deletions = $this->tracking_list->delete_persons($people_statuses->missing);
|
||||
[$undeletions, $status_changes] = $this->tracking_list->update_statuses($people_statuses->results);
|
||||
$new_undeletions = $this->tracking_list->undelete_persons(array_keys($people_statuses->results));
|
||||
$new_status_changes = $this->tracking_list->update_statuses(
|
||||
array_combine(
|
||||
array_keys($people_statuses->results),
|
||||
array_map(fn($it) => $it["status"], $people_statuses->results)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// Send mails
|
||||
|
@ -100,11 +106,11 @@ class UpdateTrackingsAction extends Action
|
|||
foreach ($this->tracking_list->list_trackers($new_deletion) as $user_email)
|
||||
$this->mailer->queue_email(new NotifyArticleDeletedEmail($user_email, $new_deletion));
|
||||
|
||||
foreach ($undeletions as $undeletion)
|
||||
foreach ($new_undeletions as $undeletion)
|
||||
foreach ($this->tracking_list->list_trackers($undeletion) as $user_email)
|
||||
$this->mailer->queue_email(new NotifyArticleUndeletedEmail($user_email, $undeletion));
|
||||
|
||||
foreach ($status_changes as $person_name => $person_status)
|
||||
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));
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ use com\fwdekker\deathnotifier\Action;
|
|||
use com\fwdekker\deathnotifier\validator\IsBooleanRule;
|
||||
use com\fwdekker\deathnotifier\validator\IsValidCsrfTokenRule;
|
||||
use com\fwdekker\deathnotifier\UnexpectedException;
|
||||
use com\fwdekker\deathnotifier\validator\IsSetRule;
|
||||
use com\fwdekker\deathnotifier\validator\InvalidInputException;
|
||||
use com\fwdekker\deathnotifier\validator\RuleSet;
|
||||
use com\fwdekker\deathnotifier\validator\SessionRuleSet;
|
||||
|
|
|
@ -150,13 +150,25 @@ class UserList
|
|||
* Returns all data of the user with the given UUID.
|
||||
*
|
||||
* @param string $uuid the UUID of the user to return the data of
|
||||
* @return array<string, mixed>|null all data of the user with the given UUID, or `null` if the user could not be
|
||||
* found
|
||||
* @return array{"uuid": string, "email": string, "email_verification_token": string|null,
|
||||
* "email_verification_token_timestamp": int, "email_notifications_enabled": int, "password": string,
|
||||
* "password_last_change": int, "password_reset_token": string|null,
|
||||
* "password_reset_token_timestamp": int}|null all data of the user with the given UUID, or `null` if the user
|
||||
* could not be found
|
||||
*/
|
||||
// TODO: Specify the return signature *exactly*
|
||||
public function get_user_by_uuid(string $uuid): ?array
|
||||
{
|
||||
$stmt = $this->database->conn->prepare("SELECT * FROM users WHERE uuid=:uuid;");
|
||||
$stmt = $this->database->conn->prepare("SELECT uuid,
|
||||
email,
|
||||
email_verification_token,
|
||||
email_verification_token_timestamp,
|
||||
email_notifications_enabled,
|
||||
password,
|
||||
password_last_change,
|
||||
password_reset_token,
|
||||
password_reset_token_timestamp
|
||||
FROM users
|
||||
WHERE uuid=:uuid;");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->execute();
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
@ -167,12 +179,25 @@ class UserList
|
|||
* Returns all data of the user with the given email address.
|
||||
*
|
||||
* @param string $email the email address of the user to return the data of
|
||||
* @return array<string, mixed>|null all data of the user with the given email address, or `null` if the user could
|
||||
* not be found
|
||||
* @return array{"uuid": string, "email": string, "email_verification_token": string|null,
|
||||
* "email_verification_token_timestamp": int, "email_notifications_enabled": int, "password": string,
|
||||
* "password_last_change": int, "password_reset_token": string|null,
|
||||
* "password_reset_token_timestamp": int}|null all data of the user with the given email address, or `null` if
|
||||
* the user could not be found
|
||||
*/
|
||||
public function get_user_by_email(string $email): ?array
|
||||
{
|
||||
$stmt = $this->database->conn->prepare("SELECT * FROM users WHERE email=:email;");
|
||||
$stmt = $this->database->conn->prepare("SELECT uuid,
|
||||
email,
|
||||
email_verification_token,
|
||||
email_verification_token_timestamp,
|
||||
email_notifications_enabled,
|
||||
password,
|
||||
password_last_change,
|
||||
password_reset_token,
|
||||
password_reset_token_timestamp
|
||||
FROM users
|
||||
WHERE email=:email;");
|
||||
$stmt->bindValue(":email", $email);
|
||||
$stmt->execute();
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
|
|
@ -72,10 +72,11 @@ class ValidatePasswordResetTokenAction extends Action
|
|||
if ($user_data === null)
|
||||
throw new InvalidInputException("No user with that email address has been registered.");
|
||||
if ($reset_token !== $user_data["password_reset_token"])
|
||||
// TODO: Just tell the user why the link is invalid: Because no request exists, or because the token is wrong
|
||||
// TODO: Also, tell the user what they can do to resolve this
|
||||
throw new InvalidInputException(
|
||||
"This password reset link is invalid. Maybe you already reset your password?"
|
||||
"This password reset link is invalid. " .
|
||||
"This may happen if you recently changed your email address or you requested another " .
|
||||
"password reset link, in which case a new password reset link should arrive in your inbox soon. " .
|
||||
"If this does not happen, log in and request a new password reset email."
|
||||
);
|
||||
|
||||
$minutes_left = Util::minutes_until_interval_elapsed(
|
||||
|
|
|
@ -56,11 +56,14 @@ class VerifyEmailAction extends Action
|
|||
$user_data = $this->user_list->get_user_by_email($inputs["email"]);
|
||||
if ($user_data === null)
|
||||
throw new InvalidInputException("No user with that email address has been registered.");
|
||||
if ($user_data["email_verification_token"] !== $inputs["verify_token"])
|
||||
// TODO: Just tell the user whether the account has been verified or whether the token is plain wrong
|
||||
// TODO: Also, tell the user what they can do to resolve this
|
||||
if ($user_data["email_verification_token"] === null)
|
||||
throw new InvalidInputException("Your email address is already verified. You can close this page.");
|
||||
if ($inputs["verify_token"] !== $user_data["email_verification_token"])
|
||||
throw new InvalidInputException(
|
||||
"Failed to verify email address. Maybe you already verified your email address?"
|
||||
"This verification link is invalid. " .
|
||||
"This may happen if you recently changed your email address or you requested another " .
|
||||
"verification link, in which case a new verification link should arrive in your inbox soon. " .
|
||||
"If this does not happen, log in and request a new verification email."
|
||||
);
|
||||
|
||||
$minutes_left = Util::minutes_until_interval_elapsed(
|
||||
|
|
Loading…
Reference in New Issue