Implement option to replace existing alias when renaming an account; use database transactions when editing accounts
This commit is contained in:
parent
6ace072841
commit
36b8cfe8b1
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MailAccountAdmin\Exceptions;
|
||||||
|
|
||||||
|
class AliasNotFoundException extends AppException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -73,26 +73,47 @@ class AccountHandler
|
||||||
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
|
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use database transactions (beginTransaction/commit/rollBack in BaseRepository maybe?)
|
|
||||||
|
|
||||||
$newUsername = $editData->getUsername();
|
$newUsername = $editData->getUsername();
|
||||||
if ($newUsername === $account->getUsername()) {
|
if ($newUsername === $account->getUsername()) {
|
||||||
// Username is unchanged
|
// Username is unchanged
|
||||||
$newUsername = null;
|
$newUsername = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This feature is not supported yet. If the alias exists, the availability check (next) would fail anyway.
|
// This variable will be set to true if the user wants to change their username, has an existing alias with this username,
|
||||||
if ($editData->getUsernameReplaceAlias()) {
|
// and checked the "replace existing alias" option.
|
||||||
throw new InputValidationError('Replace alias: Not implemented yet.');
|
$aliasNeedsToBeReplaced = false;
|
||||||
}
|
|
||||||
|
|
||||||
// Check if new username is still available
|
// Check if new username is still available
|
||||||
if ($newUsername !== null) {
|
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.");
|
throw new InputValidationError("Username \"$newUsername\" is not available.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start database transaction
|
||||||
|
$this->accountRepository->beginTransaction();
|
||||||
|
|
||||||
// Create alias for old username (if wanted)
|
// Create alias for old username (if wanted)
|
||||||
if ($editData->getUsernameCreateAlias()) {
|
if ($editData->getUsernameCreateAlias()) {
|
||||||
$oldUsername = $account->getUsername();
|
$oldUsername = $account->getUsername();
|
||||||
|
|
@ -119,9 +140,11 @@ class AccountHandler
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove existing alias for new username (if wanted)
|
// Remove existing alias for new username (if wanted)
|
||||||
// TODO: See above. This is the point where the alias should be deleted.
|
if ($editData->getUsernameReplaceAlias() && $aliasNeedsToBeReplaced) {
|
||||||
// if ($editData->getUsernameReplaceAlias()) {
|
$this->aliasRepository->removeAlias($accountId, $newUsername);
|
||||||
// throw new InputValidationError('Replace alias: Not implemented yet.');
|
}
|
||||||
// }
|
|
||||||
|
// Commit database transaction
|
||||||
|
$this->accountRepository->commitTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace MailAccountAdmin\Repositories;
|
namespace MailAccountAdmin\Repositories;
|
||||||
|
|
||||||
|
use MailAccountAdmin\Exceptions\AliasNotFoundException;
|
||||||
|
use MailAccountAdmin\Models\Alias;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
class AliasRepository extends BaseRepository
|
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 = $this->pdo->prepare('SELECT * FROM mail_aliases WHERE user_id = :user_id ORDER BY mail_address');
|
||||||
$statement->execute(['user_id' => $userId]);
|
$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
|
public function checkAliasAvailable(string $mailAddress): bool
|
||||||
|
|
@ -29,4 +51,14 @@ class AliasRepository extends BaseRepository
|
||||||
'mail_address' => $mailAddress,
|
'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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,24 @@ class BaseRepository
|
||||||
{
|
{
|
||||||
$this->pdo = $pdo;
|
$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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<p>You are about to delete the mail account "{{ accountUsername }}" including the following aliases:</p>
|
<p>You are about to delete the mail account "{{ accountUsername }}" including the following aliases:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for alias in aliases %}
|
{% for alias in aliases %}
|
||||||
<li>{{ alias['mail_address'] }}</li>
|
<li>{{ alias.getMailAddress() }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% for alias in aliases %}
|
{% for alias in aliases %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ alias['mail_address'] }}</td>
|
<td>{{ alias.getMailAddress() }}</td>
|
||||||
<td>{{ alias['created_at'] }}</td>
|
<td>{{ alias.getCreatedAt() | date }}</td>
|
||||||
<td><a href="#">Delete</a></td> {# TODO #}
|
<td><a href="#">Delete</a></td> {# TODO #}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue