Reuse single connection throughout entire query

This commit is contained in:
Florine W. Dekker 2022-08-23 12:22:57 +02:00
parent a2d39ac36c
commit 2c98d4e31e
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
8 changed files with 118 additions and 98 deletions

View File

@ -1,7 +1,7 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "0.0.17",
"version": "0.0.18",
"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.0.17",
"version": "0.0.18",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -3,6 +3,7 @@
use Monolog\ErrorHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use php\Database;
use php\Mailer;
use php\Mediawiki;
use php\Response;
@ -28,14 +29,18 @@ $logger = new Logger("main");
$logger->pushHandler(new StreamHandler($config["logger"]["file"], $config["logger"]["level"]));
ErrorHandler::register($logger);
// Connect to database
$db_exists = file_exists($config["database"]["filename"]);
$conn = Database::connect($config["database"]["filename"]);
// Instantiate utility classes
$mediawiki = new Mediawiki($logger->withName("Mediawiki"));
$user_manager = new UserManager($logger->withName("UserManager"), $config["database"]["filename"]);
$tracking_manager = new TrackingManager($logger->withName("TrackingManager"), $mediawiki, $config["database"]["filename"]);
$user_manager = new UserManager($logger->withName("UserManager"), $conn);
$tracking_manager = new TrackingManager($logger->withName("TrackingManager"), $mediawiki, $conn);
$mailer = new Mailer($logger->withName("Mailer"), $config);
// Create db if it does not exist
if (!file_exists($config["database"]["filename"])) {
if (!$db_exists) {
$logger->warning("Database does not exist. Creating new database at '" . $config["database"]["filename"] . "'.");
$user_manager->install();
@ -218,6 +223,7 @@ if (isset($_POST["action"])) {
if (!$response->satisfied) {
session_destroy();
session_start();
$_SESSION["token"] = generate_csrf_token($logger);
}
break;
case "get-user-data":

View File

@ -48,6 +48,7 @@
<!-- TODO: Forgot password option -->
<form id="loginForm" novalidate>
<p class="formValidationInfo">
<!-- TODO: Make `formValidationInfo` elements closable with an X symbol -->
<span class="validationInfo"></span>
</p>
<label for="loginEmail">
@ -90,6 +91,7 @@
<input id="registerPasswordConfirm" type="password" name="passwordConfirm" />
<span class="validationInfo"></span>
</label>
<!-- TODO: Display loading indicator while waiting for form to complete -->
<button id="registerButton">Create account</button>
</form>
</div>

View File

@ -20,27 +20,27 @@ class TrackingManager
* @var Logger The logger to use for logging.
*/
private Logger $logger;
/**
* @var string The filename of the database to interact with.
*/
private string $db_filename;
/**
* @var Mediawiki The Mediawiki instance to use for interacting with Wikipedia.
*/
private Mediawiki $mediawiki;
/**
* @var PDO The database connection to interact with.
*/
private PDO $conn;
/**
* Constructs a new tracking manager.
*
* @param Logger $logger the logger to use for logging
* @param string $db_filename the filename of the database to interact with
* @param PDO $conn the database connection to interact with
*/
public function __construct(Logger $logger, Mediawiki $mediawiki, string $db_filename)
public function __construct(Logger $logger, Mediawiki $mediawiki, PDO $conn)
{
$this->logger = $logger;
$this->mediawiki = $mediawiki;
$this->db_filename = $db_filename;
$this->conn = $conn;
}
@ -51,12 +51,11 @@ class TrackingManager
*/
public function install(): void
{
$conn = Database::connect($this->db_filename);
$conn->exec("CREATE TABLE trackings(user_uuid text not null,
person_name text not null,
status text not null,
deleted int default 0,
PRIMARY KEY (user_uuid, person_name));");
$this->conn->exec("CREATE TABLE trackings(user_uuid text not null,
person_name text not null,
status text not null,
deleted int not null default 0,
PRIMARY KEY (user_uuid, person_name));");
}
/**
@ -94,31 +93,30 @@ class TrackingManager
satisfied: false
);
$conn = Database::connect($this->db_filename);
$conn->beginTransaction();
$this->conn->beginTransaction();
$stmt = $conn->prepare("SELECT COUNT(*) as count
FROM trackings
WHERE user_uuid=:user_uuid AND person_name=:person_name;");
$stmt = $this->conn->prepare("SELECT COUNT(*) as count
FROM trackings
WHERE user_uuid=:user_uuid AND person_name=:person_name;");
$stmt->bindValue(":user_uuid", $user_uuid);
$stmt->bindValue(":person_name", $normalized_name);
$stmt->execute();
if ($stmt->fetch(PDO::FETCH_ASSOC)["count"] > 0) {
$conn->rollBack();
$this->conn->rollBack();
return new Response(
payload: ["target" => "personName", "message" => "You are already tracking this person."],
satisfied: false
);
}
$stmt = $conn->prepare("INSERT INTO trackings (user_uuid, person_name, status)
VALUES (:user_uuid, :person_name, :status);");
$stmt = $this->conn->prepare("INSERT INTO trackings (user_uuid, person_name, status)
VALUES (:user_uuid, :person_name, :status);");
$stmt->bindValue(":user_uuid", $user_uuid);
$stmt->bindValue(":person_name", $normalized_name);
$stmt->bindValue(":status", $status);
$stmt->execute();
$conn->commit();
$this->conn->commit();
return new Response(payload: null, satisfied: true);
}
@ -137,9 +135,8 @@ class TrackingManager
satisfied: false
);
$conn = Database::connect($this->db_filename);
$stmt = $conn->prepare("DELETE FROM trackings
WHERE user_uuid=:user_uuid AND person_name=:person_name;");
$stmt = $this->conn->prepare("DELETE FROM trackings
WHERE user_uuid=:user_uuid AND person_name=:person_name;");
$stmt->bindValue(":user_uuid", $user_uuid);
$stmt->bindValue(":person_name", $person_name);
$stmt->execute();
@ -155,10 +152,9 @@ class TrackingManager
*/
public function list_trackings(string $user_uuid): Response
{
$conn = Database::connect($this->db_filename);
$stmt = $conn->prepare("SELECT person_name, status, deleted
FROM trackings
WHERE user_uuid=:user_uuid;");
$stmt = $this->conn->prepare("SELECT person_name, status, deleted
FROM trackings
WHERE user_uuid=:user_uuid;");
$stmt->bindValue(":user_uuid", $user_uuid);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -173,8 +169,7 @@ class TrackingManager
*/
public function list_all_unique_person_names(): array
{
$conn = Database::connect($this->db_filename);
$stmt = $conn->prepare("SELECT DISTINCT person_name FROM trackings;");
$stmt = $this->conn->prepare("SELECT DISTINCT person_name FROM trackings;");
$stmt->execute();
return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), "person_name");
}
@ -189,13 +184,13 @@ class TrackingManager
{
$people_statuses = $this->mediawiki->people_statuses($people_names);
$conn = Database::connect($this->db_filename);
$conn->beginTransaction();
// Begin transaction
$this->conn->beginTransaction();
// Process redirects
$stmt = $conn->prepare("UPDATE trackings
SET person_name=:new_person_name
WHERE person_name=:old_person_name;");
$stmt = $this->conn->prepare("UPDATE trackings
SET person_name=:new_person_name
WHERE person_name=:old_person_name;");
$stmt->bindParam(":old_person_name", $from);
$stmt->bindParam(":new_person_name", $to);
foreach ($people_statuses->redirects as $from => $to)
@ -203,22 +198,23 @@ class TrackingManager
$stmt->execute();
// Process deletions
$stmt = $conn->prepare("UPDATE trackings
SET deleted=1
WHERE person_name=:person_name;");
$stmt = $this->conn->prepare("UPDATE trackings
SET deleted=1
WHERE person_name=:person_name;");
$stmt->bindParam(":person_name", $missing_title);
foreach ($people_statuses->missing as $missing_title)
$stmt->execute();
// Process status changes
$stmt = $conn->prepare("UPDATE trackings
SET status=:status, deleted=0
WHERE person_name=:person_name;");
$stmt = $this->conn->prepare("UPDATE trackings
SET status=:status, deleted=0
WHERE person_name=:person_name;");
$stmt->bindParam(":status", $person_status);
$stmt->bindParam(":person_name", $person_name);
foreach ($people_statuses->results as $person_name => $person_status)
$stmt->execute();
$conn->commit();
// Commit transaction
$this->conn->commit();
}
}

View File

@ -31,21 +31,21 @@ class UserManager
*/
private Logger $logger;
/**
* @var string The filename of the database to interact with.
* @var PDO The database connection to interact with.
*/
private string $db_filename;
private PDO $conn;
/**
* Constructs a new user manager.
*
* @param Logger $logger the logger to use for logging
* @param string $db_filename the filename of the database to interact with
* @param PDO $conn the database connection to interact with
*/
public function __construct(Logger $logger, string $db_filename)
public function __construct(Logger $logger, PDO $conn)
{
$this->logger = $logger;
$this->db_filename = $db_filename;
$this->conn = $conn;
}
@ -56,10 +56,13 @@ class UserManager
*/
public function install(): void
{
$conn = Database::connect($this->db_filename);
$conn->exec("CREATE TABLE users(uuid text primary key not null, email text not null,
email_is_verified int not null default 0, email_verification_token text,
password text not null, password_update_time int not null);");
$this->conn->exec("CREATE TABLE users(uuid text not null,
email text not null,
email_is_verified int not null default 0,
email_verification_token text,
password text not null,
password_update_time int not null,
PRIMARY KEY (uuid));");
}
/**
@ -106,17 +109,18 @@ class UserManager
satisfied: false
);
// Connect
$conn = Database::connect($this->db_filename);
$conn->beginTransaction();
// Begin transaction
$this->conn->beginTransaction();
// Check if email address is already in use
$stmt = $conn->prepare("SELECT COUNT(*) as count FROM users WHERE email=:email;");
$stmt = $this->conn->prepare("SELECT COUNT(*) as count
FROM users
WHERE email=:email;");
$stmt->bindValue(":email", $email);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result["count"] > 0) {
$conn->rollBack();
$this->conn->rollBack();
return new Response(
payload: ["target" => "email", "message" => "Email address already in use."],
satisfied: false
@ -124,8 +128,10 @@ class UserManager
}
// Register user
$stmt = $conn->prepare("INSERT INTO users (uuid, email, email_verification_token, password, password_update_time)
VALUES (:uuid, :email, :email_verification_token, :password, unixepoch());");
$stmt = $this->conn->prepare("INSERT INTO users (uuid, email, email_verification_token,
password, password_update_time)
VALUES (:uuid, :email, :email_verification_token,
:password, unixepoch());");
$stmt->bindValue(":uuid", $uuid);
$stmt->bindValue(":email", $email);
$stmt->bindValue(":email_verification_token", $email_verification_token);
@ -133,7 +139,7 @@ class UserManager
$stmt->execute();
// Respond
$conn->commit();
$this->conn->commit();
$mailer->send_registration_email($email, $email_verification_token);
return new Response(payload: null, satisfied: true);
}
@ -147,10 +153,11 @@ class UserManager
*/
public function delete(string $uuid): Response
{
$conn = Database::connect($this->db_filename);
$stmt = $this->conn->prepare("DELETE FROM users WHERE uuid=:uuid;");
$stmt->bindValue(":uuid", $uuid);
$stmt->execute();
// TODO: Delete trackings!
$stmt = $conn->prepare("DELETE FROM users WHERE uuid=:uuid;");
$stmt = $this->conn->prepare("DELETE FROM trackings WHERE user_uuid=:uuid");
$stmt->bindValue(":uuid", $uuid);
$stmt->execute();
@ -178,10 +185,9 @@ class UserManager
satisfied: false
);
$conn = Database::connect($this->db_filename);
$stmt = $conn->prepare("SELECT uuid, email, email_is_verified, password, password_update_time
FROM users
WHERE email=:email;");
$stmt = $this->conn->prepare("SELECT uuid, email, email_is_verified, password, password_update_time
FROM users
WHERE email=:email;");
$stmt->bindValue(":email", $email);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -206,8 +212,9 @@ class UserManager
*/
public function get_user_data(string $uuid): Response
{
$conn = Database::connect($this->db_filename);
$stmt = $conn->prepare("SELECT uuid, email, email_is_verified, password_update_time FROM users WHERE uuid=:uuid;");
$stmt = $this->conn->prepare("SELECT uuid, email, email_is_verified, password_update_time
FROM users
WHERE uuid=:uuid;");
$stmt->bindValue(":uuid", $uuid);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
@ -249,12 +256,11 @@ class UserManager
satisfied: false
);
// Connect
$conn = Database::connect($this->db_filename);
$conn->beginTransaction();
// Begin transaction
$this->conn->beginTransaction();
// Check if email address is different
$stmt = $conn->prepare("SELECT email FROM users WHERE uuid=:uuid;");
$stmt = $this->conn->prepare("SELECT email FROM users WHERE uuid=:uuid;");
$stmt->bindValue(":uuid", $uuid);
$stmt->execute();
$email_old = $stmt->fetch(PDO::FETCH_ASSOC)["email"];
@ -265,11 +271,13 @@ class UserManager
);
// Check if email address is already in use
$stmt = $conn->prepare("SELECT COUNT(*) as count FROM users WHERE email=:email;");
$stmt = $this->conn->prepare("SELECT COUNT(*) as count
FROM users
WHERE email=:email;");
$stmt->bindValue(":email", $email);
$stmt->execute();
if ($stmt->fetch(PDO::FETCH_ASSOC)["count"] > 0) {
$conn->rollBack();
$this->conn->rollBack();
return new Response(
payload: ["target" => "email", "message" => "Email address already in use."],
satisfied: false
@ -277,14 +285,18 @@ class UserManager
}
// Update email address
$stmt = $conn->prepare("UPDATE users SET email=:email, email_is_verified=0, email_verification_token=:email_verification_token WHERE uuid=:uuid;");
$stmt = $this->conn->prepare("UPDATE users
SET email=:email,
email_is_verified=0,
email_verification_token=:email_verification_token
WHERE uuid=:uuid;");
$stmt->bindValue(":uuid", $uuid);
$stmt->bindValue(":email", $email);
$stmt->bindValue(":email_verification_token", $email_verification_token);
$stmt->execute();
// Respond
$conn->commit();
$this->conn->commit();
$mailer->send_email_verification($email_old, $email, $email_verification_token);
return new Response(payload: null, satisfied: true);
}
@ -304,16 +316,18 @@ class UserManager
satisfied: false
);
$conn = Database::connect($this->db_filename);
$conn->beginTransaction();
$this->conn->beginTransaction();
$stmt = $conn->prepare("SELECT email_is_verified, email_verification_token FROM users WHERE email=:email;");
$stmt = $this->conn->prepare("SELECT email_is_verified, email_verification_token
FROM users
WHERE email=:email;");
$stmt->bindValue(":email", $email);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result === false || $result["email_verification_token"] === null || !hash_equals($result["email_verification_token"], $email_verification_token)) {
$conn->rollBack();
if ($result === false || $result["email_verification_token"] === null
|| !hash_equals($result["email_verification_token"], $email_verification_token)) {
$this->conn->rollBack();
return new Response(
payload: ["target" => "email", "message" => "Failed to verify email address."],
satisfied: false
@ -321,15 +335,17 @@ class UserManager
}
if ($result["email_is_verified"]) {
$conn->commit();
$this->conn->commit();
return new Response(payload: null, satisfied: true);
}
$stmt = $conn->prepare("UPDATE users SET email_is_verified=1, email_verification_token=null WHERE email=:email;");
$stmt = $this->conn->prepare("UPDATE users
SET email_is_verified=1, email_verification_token=null
WHERE email=:email;");
$stmt->bindValue(":email", $email);
$stmt->execute();
$conn->commit();
$this->conn->commit();
return new Response(payload: null, satisfied: true);
}
@ -366,17 +382,16 @@ class UserManager
satisfied: false
);
// Connect
$conn = Database::connect($this->db_filename);
$conn->beginTransaction();
// Begin transaction
$this->conn->beginTransaction();
// Validate old password
$stmt = $conn->prepare("SELECT password FROM users WHERE uuid=:uuid;");
$stmt = $this->conn->prepare("SELECT password FROM users WHERE uuid=:uuid;");
$stmt->bindValue(":uuid", $uuid);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!password_verify($password_old, $user["password"])) {
$conn->rollBack();
$this->conn->rollBack();
return new Response(
payload: ["target" => "passwordOld", "message" => "Incorrect old password."],
satisfied: false
@ -384,14 +399,15 @@ class UserManager
}
// Update password
$stmt = $conn->prepare("UPDATE users SET password=:password, password_update_time=unixepoch()
WHERE uuid=:uuid;");
$stmt = $this->conn->prepare("UPDATE users
SET password=:password, password_update_time=unixepoch()
WHERE uuid=:uuid;");
$stmt->bindValue(":uuid", $uuid);
$stmt->bindValue(":password", password_hash($password_new, PASSWORD_DEFAULT));
$stmt->execute();
// Respond
$conn->commit();
$this->conn->commit();
return new Response(payload: null, satisfied: true);
}
}