Implement option to replace existing alias when renaming an account; use database transactions when editing accounts

This commit is contained in:
Lexi / Zoe 2021-08-20 22:48:15 +02:00
parent 6ace072841
commit 36b8cfe8b1
Signed by: binaryDiv
GPG Key ID: F8D4956E224DA232
7 changed files with 163 additions and 15 deletions

View File

@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace MailAccountAdmin\Exceptions;
class AliasNotFoundException extends AppException
{
}

View File

@ -73,26 +73,47 @@ class AccountHandler
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
}
// TODO: Use database transactions (beginTransaction/commit/rollBack in BaseRepository maybe?)
$newUsername = $editData->getUsername();
if ($newUsername === $account->getUsername()) {
// Username is unchanged
$newUsername = null;
}
// TODO: This feature is not supported yet. If the alias exists, the availability check (next) would fail anyway.
if ($editData->getUsernameReplaceAlias()) {
throw new InputValidationError('Replace alias: Not implemented yet.');
}
// This variable will be set to true if the user wants to change their username, has an existing alias with this username,
// and checked the "replace existing alias" option.
$aliasNeedsToBeReplaced = false;
// Check if new username is still available
if ($newUsername !== null) {
if (!$this->accountRepository->checkUsernameAvailable($newUsername) || !$this->aliasRepository->checkAliasAvailable($newUsername)) {
$newUsernameAvailable = true;
// Check if account with this username already exists
if (!$this->accountRepository->checkUsernameAvailable($newUsername)) {
$newUsernameAvailable = false;
}
// Check if alias with this username/address already exists
if (!$this->aliasRepository->checkAliasAvailable($newUsername)) {
$newUsernameAvailable = false;
// Alias already exists. If user wants to replace an existing alias, check if the alias belongs to this user.
if ($editData->getUsernameReplaceAlias()) {
$existingAlias = $this->aliasRepository->fetchAliasByAddress($newUsername);
if ($existingAlias->getUserId() === $accountId) {
$aliasNeedsToBeReplaced = true;
$newUsernameAvailable = true;
}
}
}
if (!$newUsernameAvailable) {
throw new InputValidationError("Username \"$newUsername\" is not available.");
}
}
// Start database transaction
$this->accountRepository->beginTransaction();
// Create alias for old username (if wanted)
if ($editData->getUsernameCreateAlias()) {
$oldUsername = $account->getUsername();
@ -119,9 +140,11 @@ class AccountHandler
);
// Remove existing alias for new username (if wanted)
// TODO: See above. This is the point where the alias should be deleted.
// if ($editData->getUsernameReplaceAlias()) {
// throw new InputValidationError('Replace alias: Not implemented yet.');
// }
if ($editData->getUsernameReplaceAlias() && $aliasNeedsToBeReplaced) {
$this->aliasRepository->removeAlias($accountId, $newUsername);
}
// Commit database transaction
$this->accountRepository->commitTransaction();
}
}

65
src/Models/Alias.php Normal file
View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace MailAccountAdmin\Models;
use DateTimeImmutable;
class Alias
{
/** @var int */
private $id;
/** @var int */
private $userId;
/** @var string */
private $mailAddress;
/** @var DateTimeImmutable */
private $createdAt;
/** @var DateTimeImmutable */
private $modifiedAt;
private function __construct(int $id, int $userId, string $mailAddress, DateTimeImmutable $createdAt, DateTimeImmutable $modifiedAt)
{
$this->id = $id;
$this->userId = $userId;
$this->mailAddress = $mailAddress;
$this->createdAt = $createdAt;
$this->modifiedAt = $modifiedAt;
}
public static function createFromArray(array $data): self
{
return new self(
(int)$data['alias_id'],
(int)$data['user_id'],
$data['mail_address'],
new DateTimeImmutable($data['created_at']),
new DateTimeImmutable($data['modified_at']),
);
}
public function getId(): int
{
return $this->id;
}
public function getUserId(): int
{
return $this->userId;
}
public function getMailAddress(): string
{
return $this->mailAddress;
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function getModifiedAt(): DateTimeImmutable
{
return $this->modifiedAt;
}
}

View File

@ -3,6 +3,8 @@ declare(strict_types=1);
namespace MailAccountAdmin\Repositories;
use MailAccountAdmin\Exceptions\AliasNotFoundException;
use MailAccountAdmin\Models\Alias;
use PDO;
class AliasRepository extends BaseRepository
@ -11,7 +13,27 @@ class AliasRepository extends BaseRepository
{
$statement = $this->pdo->prepare('SELECT * FROM mail_aliases WHERE user_id = :user_id ORDER BY mail_address');
$statement->execute(['user_id' => $userId]);
return $statement->fetchAll(PDO::FETCH_ASSOC);
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
// Create Account models from rows
$aliasList = [];
foreach ($rows as $row) {
$aliasList[] = Alias::createFromArray($row);
}
return $aliasList;
}
public function fetchAliasByAddress(string $mailAddress): Alias
{
$statement = $this->pdo->prepare('SELECT * FROM mail_aliases WHERE mail_address = :mail_address LIMIT 1');
$statement->execute(['mail_address' => $mailAddress]);
if ($statement->rowCount() < 1) {
throw new AliasNotFoundException("Alias '$mailAddress' was not found.");
}
$row = $statement->fetch(PDO::FETCH_ASSOC);
return Alias::createFromArray($row);
}
public function checkAliasAvailable(string $mailAddress): bool
@ -29,4 +51,14 @@ class AliasRepository extends BaseRepository
'mail_address' => $mailAddress,
]);
}
public function removeAlias(int $userId, string $mailAddress): void
{
// Check user ID in WHERE clause to make sure we don't accidentally delete someone else's alias
$statement = $this->pdo->prepare('DELETE FROM mail_aliases WHERE user_id = :user_id AND mail_address = :mail_address LIMIT 1');
$statement->execute([
'user_id' => $userId,
'mail_address' => $mailAddress,
]);
}
}

View File

@ -14,4 +14,24 @@ class BaseRepository
{
$this->pdo = $pdo;
}
public function beginTransaction(): void
{
$this->pdo->beginTransaction();
}
public function commitTransaction(): void
{
$this->pdo->commit();
}
public function rollBackTransaction(): void
{
$this->pdo->rollBack();
}
public function inTransaction(): bool
{
return $this->pdo->inTransaction();
}
}

View File

@ -18,7 +18,7 @@
<p>You are about to delete the mail account "{{ accountUsername }}" including the following aliases:</p>
<ul>
{% for alias in aliases %}
<li>{{ alias['mail_address'] }}</li>
<li>{{ alias.getMailAddress() }}</li>
{% endfor %}
</ul>

View File

@ -73,8 +73,8 @@
</tr>
{% for alias in aliases %}
<tr>
<td>{{ alias['mail_address'] }}</td>
<td>{{ alias['created_at'] }}</td>
<td>{{ alias.getMailAddress() }}</td>
<td>{{ alias.getCreatedAt() | date }}</td>
<td><a href="#">Delete</a></td> {# TODO #}
</tr>
{% endfor %}