Reuse single connection throughout entire query
This commit is contained in:
parent
a2d39ac36c
commit
2c98d4e31e
|
@ -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",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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",
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue