Implement deleting aliases on account details page
This commit is contained in:
parent
57b712300f
commit
2075aca98b
|
|
@ -9,7 +9,7 @@ abstract class FormData
|
|||
{
|
||||
// Abstract methods
|
||||
|
||||
abstract public static function createFromArray($raw): self;
|
||||
abstract public static function createFromArray(array $raw): self;
|
||||
|
||||
|
||||
// Input validation - Base types
|
||||
|
|
@ -26,6 +26,16 @@ abstract class FormData
|
|||
return $raw;
|
||||
}
|
||||
|
||||
protected static function validateInteger(string $raw, bool $required = true, string $fieldName = 'Field'): int
|
||||
{
|
||||
if ($raw === '' && $required) {
|
||||
throw new InputValidationError("$fieldName is required.");
|
||||
} elseif (!is_numeric($raw)) {
|
||||
throw new InputValidationError("$fieldName is not a number.");
|
||||
}
|
||||
return (int)$raw;
|
||||
}
|
||||
|
||||
protected static function validateBoolOption(string $raw): bool
|
||||
{
|
||||
return $raw !== '';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class AccountAddAliasData extends FormData
|
|||
$this->aliasAddress = $aliasAddress;
|
||||
}
|
||||
|
||||
public static function createFromArray($raw): self
|
||||
public static function createFromArray(array $raw): self
|
||||
{
|
||||
return new self(
|
||||
self::validateAliasAddress(trim($raw['alias_address'] ?? '')),
|
||||
|
|
|
|||
|
|
@ -236,7 +236,33 @@ class AccountController extends BaseController
|
|||
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($e->getMessage(), $addAliasData));
|
||||
}
|
||||
|
||||
// Redirect to edit form page via GET (PRG)
|
||||
return $response->withHeader('Location', '/accounts/' . $accountId)->withStatus(303);
|
||||
// Redirect to account details page via GET (PRG)
|
||||
return $response->withHeader('Location', '/accounts/' . $accountId . '#aliases')->withStatus(303);
|
||||
}
|
||||
|
||||
|
||||
// -- /accounts/{id}/deletealiases - Deletes a list of aliases
|
||||
|
||||
public function deleteAliasesFromAccount(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
// Parse URL arguments and form data
|
||||
$accountId = (int)$args['id'];
|
||||
$deleteAliasesData = $request->getParsedBody();
|
||||
|
||||
try {
|
||||
// Validate input
|
||||
$validatedDeleteAliasesData = AccountDeleteAliasesData::createFromArray($deleteAliasesData);
|
||||
$deletedCount = $this->accountHandler->deleteAliasesFromAccount($accountId, $validatedDeleteAliasesData);
|
||||
|
||||
// Save success result
|
||||
$successMessage = $deletedCount === 1 ? "1 alias was deleted." : "{$deletedCount} aliases were deleted.";
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult($successMessage));
|
||||
} catch (AppException $e) {
|
||||
// Save error result
|
||||
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($e->getMessage()));
|
||||
}
|
||||
|
||||
// Redirect to account details page via GET (PRG)
|
||||
return $response->withHeader('Location', '/accounts/' . $accountId . '#aliases')->withStatus(303);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class AccountCreateData extends FormData
|
|||
$this->memo = $memo;
|
||||
}
|
||||
|
||||
public static function createFromArray($raw): self
|
||||
public static function createFromArray(array $raw): self
|
||||
{
|
||||
return new self(
|
||||
self::validateUsername(trim($raw['username'] ?? '')),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MailAccountAdmin\Frontend\Accounts;
|
||||
|
||||
use MailAccountAdmin\Common\FormData;
|
||||
use MailAccountAdmin\Exceptions\InputValidationError;
|
||||
|
||||
class AccountDeleteAliasesData extends FormData
|
||||
{
|
||||
/** @var array[int] */
|
||||
private array $selectedAliasIds;
|
||||
|
||||
private function __construct(array $selectedAliasIds)
|
||||
{
|
||||
$this->selectedAliasIds = $selectedAliasIds;
|
||||
}
|
||||
|
||||
public static function createFromArray(array $raw): self
|
||||
{
|
||||
$rawAliasIds = $raw['selected_aliases'] ?? [];
|
||||
|
||||
if (!is_array($rawAliasIds)) {
|
||||
throw new InputValidationError('selected_aliases is not an array.');
|
||||
}
|
||||
if (empty($rawAliasIds)) {
|
||||
throw new InputValidationError('No aliases were selected.');
|
||||
}
|
||||
|
||||
$selectedAliasIds = [];
|
||||
foreach ($rawAliasIds as $i => $id) {
|
||||
$selectedAliasIds[] = self::validateInteger($id, true, "selected_aliases[$i]");
|
||||
}
|
||||
|
||||
return new self($selectedAliasIds);
|
||||
}
|
||||
|
||||
public function getSelectedAliasIds(): array
|
||||
{
|
||||
return $this->selectedAliasIds;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ class AccountEditData extends FormData
|
|||
$this->memo = $memo;
|
||||
}
|
||||
|
||||
public static function createFromArray($raw): self
|
||||
public static function createFromArray(array $raw): self
|
||||
{
|
||||
return new self(
|
||||
self::validateUsername(trim($raw['username'] ?? ''), false),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace MailAccountAdmin\Frontend\Accounts;
|
|||
use MailAccountAdmin\Common\PasswordHelper;
|
||||
use MailAccountAdmin\Exceptions\AccountNotFoundException;
|
||||
use MailAccountAdmin\Exceptions\InputValidationError;
|
||||
use MailAccountAdmin\Models\Account;
|
||||
use MailAccountAdmin\Repositories\AccountRepository;
|
||||
use MailAccountAdmin\Repositories\AliasRepository;
|
||||
use MailAccountAdmin\Repositories\DomainRepository;
|
||||
|
|
@ -27,6 +28,17 @@ class AccountHandler
|
|||
}
|
||||
|
||||
|
||||
// -- Helper methods
|
||||
|
||||
private function ensureAccountExists(int $accountId): Account
|
||||
{
|
||||
try {
|
||||
return $this->accountRepository->fetchAccountById($accountId);
|
||||
} catch (AccountNotFoundException $e) {
|
||||
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
|
||||
}
|
||||
}
|
||||
|
||||
// -- /accounts - List all accounts
|
||||
|
||||
public function listAccounts(string $filterByDomain): array
|
||||
|
|
@ -134,11 +146,7 @@ class AccountHandler
|
|||
$returnData = [];
|
||||
|
||||
// Check if account exists
|
||||
try {
|
||||
$account = $this->accountRepository->fetchAccountById($accountId);
|
||||
} catch (AccountNotFoundException $e) {
|
||||
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
|
||||
}
|
||||
$account = $this->ensureAccountExists($accountId);
|
||||
|
||||
$newUsername = $editData->getUsername();
|
||||
if ($newUsername === $account->getUsername()) {
|
||||
|
|
@ -146,9 +154,9 @@ class AccountHandler
|
|||
$newUsername = null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// If the user wants to change their username, has an existing alias with this username and checked the "replace existing alias"
|
||||
// option, this variable will be set to the ID of the alias that needs to be deleted.
|
||||
$aliasNeedsToBeReplaced = null;
|
||||
|
||||
// Check if new username is still available
|
||||
if ($newUsername !== null) {
|
||||
|
|
@ -167,7 +175,7 @@ class AccountHandler
|
|||
if ($editData->getUsernameReplaceAlias()) {
|
||||
$existingAlias = $this->aliasRepository->fetchAliasByAddress($newUsername);
|
||||
if ($existingAlias->getUserId() === $accountId) {
|
||||
$aliasNeedsToBeReplaced = true;
|
||||
$aliasNeedsToBeReplaced = $existingAlias->getId();
|
||||
$newUsernameAvailable = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -211,8 +219,8 @@ class AccountHandler
|
|||
);
|
||||
|
||||
// Remove existing alias for new username (if wanted)
|
||||
if ($editData->getUsernameReplaceAlias() && $aliasNeedsToBeReplaced) {
|
||||
$this->aliasRepository->deleteAlias($accountId, $newUsername);
|
||||
if ($editData->getUsernameReplaceAlias() && $aliasNeedsToBeReplaced !== null) {
|
||||
$this->aliasRepository->deleteAliasById($accountId, $aliasNeedsToBeReplaced);
|
||||
}
|
||||
|
||||
// Commit database transaction
|
||||
|
|
@ -242,11 +250,7 @@ class AccountHandler
|
|||
public function deleteAccount(int $accountId): array
|
||||
{
|
||||
// Check if account exists
|
||||
try {
|
||||
$account = $this->accountRepository->fetchAccountById($accountId);
|
||||
} catch (AccountNotFoundException $e) {
|
||||
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
|
||||
}
|
||||
$account = $this->ensureAccountExists($accountId);
|
||||
|
||||
// Start database transaction
|
||||
$this->accountRepository->beginTransaction();
|
||||
|
|
@ -272,11 +276,7 @@ class AccountHandler
|
|||
public function addAliasToAccount(int $accountId, AccountAddAliasData $aliasAddData): void
|
||||
{
|
||||
// Check if account exists
|
||||
try {
|
||||
$this->accountRepository->fetchAccountById($accountId);
|
||||
} catch (AccountNotFoundException $e) {
|
||||
throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!');
|
||||
}
|
||||
$this->ensureAccountExists($accountId);
|
||||
|
||||
// Check if alias address is still available
|
||||
$address = $aliasAddData->getAliasAddress();
|
||||
|
|
@ -287,4 +287,29 @@ class AccountHandler
|
|||
// Create alias in database
|
||||
$this->aliasRepository->createNewAlias($accountId, $address);
|
||||
}
|
||||
|
||||
|
||||
// -- /accounts/{id}/deletealiases - Deletes a list of aliases from the account
|
||||
|
||||
public function deleteAliasesFromAccount(int $accountId, AccountDeleteAliasesData $aliasesDeleteData): int
|
||||
{
|
||||
// Check if account exists
|
||||
$this->ensureAccountExists($accountId);
|
||||
|
||||
// Start database transaction
|
||||
$this->aliasRepository->beginTransaction();
|
||||
|
||||
// Delete aliases
|
||||
$deletedCount = 0;
|
||||
foreach ($aliasesDeleteData->getSelectedAliasIds() as $aliasId) {
|
||||
if ($this->aliasRepository->deleteAliasById($accountId, $aliasId)) {
|
||||
$deletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Commit database transaction
|
||||
$this->aliasRepository->commitTransaction();
|
||||
|
||||
return $deletedCount;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,14 +52,15 @@ class AliasRepository extends BaseRepository
|
|||
]);
|
||||
}
|
||||
|
||||
public function deleteAlias(int $userId, string $mailAddress): void
|
||||
public function deleteAliasById(int $userId, int $aliasId): bool
|
||||
{
|
||||
// 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 = $this->pdo->prepare('DELETE FROM mail_aliases WHERE user_id = :user_id AND alias_id = :alias_id LIMIT 1');
|
||||
$statement->execute([
|
||||
'user_id' => $userId,
|
||||
'mail_address' => $mailAddress,
|
||||
'alias_id' => $aliasId,
|
||||
]);
|
||||
return $statement->rowCount() > 0;
|
||||
}
|
||||
|
||||
public function deleteAllAliasesForUserId(int $userId): int
|
||||
|
|
|
|||
|
|
@ -34,5 +34,6 @@ class Routes
|
|||
$app->get('/accounts/{id:[1-9][0-9]*}/delete', AccountController::class . ':showAccountDelete');
|
||||
$app->post('/accounts/{id:[1-9][0-9]*}/delete', AccountController::class . ':deleteAccount');
|
||||
$app->post('/accounts/{id:[1-9][0-9]*}/addalias', AccountController::class . ':addAliasToAccount');
|
||||
$app->post('/accounts/{id:[1-9][0-9]*}/deletealiases', AccountController::class . ':deleteAliasesFromAccount');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,34 +62,41 @@
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Aliases</h3>
|
||||
<h3 id="aliases">Aliases</h3>
|
||||
|
||||
{{ include('includes/form_result_box.html.twig') }}
|
||||
|
||||
{% if aliases %}
|
||||
<table class="bordered_table">
|
||||
<tr>
|
||||
<th>Address</th>
|
||||
<th>Created at</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{% for alias in aliases %}
|
||||
<form action="/accounts/{{ id }}/deletealiases" method="POST">
|
||||
<table class="bordered_table">
|
||||
<tr>
|
||||
<td>{{ alias.getMailAddress() }}</td>
|
||||
<td>{{ alias.getCreatedAt() | date }}</td>
|
||||
<td><a href="#">Delete</a></td> {# TODO #}
|
||||
<th></th>
|
||||
<th>Address</th>
|
||||
<th>Created at</th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% for alias in aliases %}
|
||||
<tr>
|
||||
<td><input type="checkbox" id="delete_selected_{{ alias.getId() }}" name="selected_aliases[]" value="{{ alias.getId() }}"/></td>
|
||||
<td><label for="delete_selected_{{ alias.getId() }}">{{ alias.getMailAddress() }}</label></td>
|
||||
<td>{{ alias.getCreatedAt() | date }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<p>
|
||||
<button type="submit">Delete selected aliases</button>
|
||||
</p>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>No aliases.</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="/accounts/{{ id }}/addalias" method="POST">
|
||||
{{ include('includes/form_result_box.html.twig') }}
|
||||
|
||||
<table class="margin_vertical_1rem">
|
||||
<tr>
|
||||
<td><label for="add_alias_address">New alias:</label></td>
|
||||
<td><input id="add_alias_address" name="alias_address" value="{{ formData['new_alias'] | default('') }}"/></td>
|
||||
<td>
|
||||
<input id="add_alias_address" name="alias_address" value="{{ formData['new_alias'] | default('') }}" {{ success is defined or error is defined ? 'autofocus': '' }}/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
|
|
|
|||
Loading…
Reference in New Issue