Skip to content

Commit

Permalink
Merge pull request #1861 from WPO-Foundation/delete-api-key
Browse files Browse the repository at this point in the history
feat(api): delete multiple keys together
  • Loading branch information
jefflembeck authored May 23, 2022
2 parents 9dcb225 + e8b9cce commit e6b26d1
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 80 deletions.
16 changes: 1 addition & 15 deletions www/cpauth/account.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,7 @@
throw new ClientException($e->getMessage(), "/account");
}
} elseif ($type == "delete-api-key") {
try {
$id = filter_input(INPUT_POST, 'api-key-id', FILTER_SANITIZE_NUMBER_INT);
$request_context->getClient()->deleteApiKey(intval($id));

$protocol = $request_context->getUrlProtocol();
$host = Util::getSetting('host');
$route = '/account';
$redirect_uri = "{$protocol}://{$host}{$route}";

header("Location: {$redirect_uri}");
exit();
} catch (Exception $e) {
error_log($e->getMessage());
throw new ClientException("There was an error", "/account");
}
AccountHandler::deleteApiKey($request_context);
} elseif ($type == "resend-verification-email") {
try {
$request_context->getClient()->resendEmailVerification();
Expand Down
15 changes: 13 additions & 2 deletions www/css/account.css
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,8 @@ table.sortable th:not([aria-sort]) button:hover span::after {
justify-content: flex-start;
}

.create-delete-button-container button {
.create-delete-button-container button,
.create-delete-button-container label {
appearance: none;
border: 0;
height: 3.4em;
Expand All @@ -1108,6 +1109,9 @@ table.sortable th:not([aria-sort]) button:hover span::after {
cursor: pointer;
color: #323232;
}
.create-delete-button-container label {
line-height: 3.6em;
}

.create-delete-button-container .new-api-key {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'%3E%3Cpath d='M17 9V10H10V17H9V10H2V9H9V2H10V9H17Z' fill='%23006AD4'/%3E%3C/svg%3E");
Expand All @@ -1117,7 +1121,7 @@ table.sortable th:not([aria-sort]) button:hover span::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'%3E%3Cpath d='M12 4V3C12 2.73478 11.8946 2.48043 11.7071 2.29289C11.5196 2.10536 11.2652 2 11 2H8C7.73478 2 7.48043 2.10536 7.29289 2.29289C7.10536 2.48043 7 2.73478 7 3V4H2V5H4V17C4 17.2652 4.10536 17.5196 4.29289 17.7071C4.48043 17.8946 4.73478 18 5 18H14C14.2652 18 14.5196 17.8946 14.7071 17.7071C14.8946 17.5196 15 17.2652 15 17V5H17V4H12ZM8 3H11V4H8V3ZM14 17H5V5H14V17ZM8 15H7V7H8V15ZM10 15H9V7H10V15ZM12 15H11V7H12V15Z' fill='%23006AD4'/%3E%3C/svg%3E");
}

.create-delete-button-container .delete-key:disabled {
.create-delete-button-container .delete-key.disabled {
color: inherit;
opacity: 0.3;
cursor: auto;
Expand Down Expand Up @@ -1205,6 +1209,13 @@ table.sortable th:not([aria-sort]) button:hover span::after {
/* API key length */
}

.api-consumers .select-all-box {
position: relative;
top: .5em;
left: 3px;
width: 3em;
}

.billing-frequency-selector {
text-align: center;
margin-bottom: 2.5rem;
Expand Down
89 changes: 89 additions & 0 deletions www/js/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,84 @@ window.SortableTable = SortableTable;

window.HiddenContent = HiddenContent;
}(window));

/**
* DeleteApiKeyBoxSet
*/
(function(window){
class DeleteApiKeyBoxSet {
constructor(mainCheckbox) {
this.selectAllBox = mainCheckbox;
const form = this.selectAllBox.closest('form');
const individualBoxes = form.querySelectorAll('[data-apikeybox=individual]');
const deleteButtonLabel = document.querySelector('[data-apikeybox=delete-button]');
const deleteButton = form.querySelector('input[type=submit]');

this.individualBoxes = individualBoxes;
this.deleteButton = deleteButton;
this.deleteButtonLabel = deleteButtonLabel;

this.selectAllBox.addEventListener('change', this.handleMainBoxChange.bind(this));
this.individualBoxes.forEach((box) => {
box.addEventListener('change', this.handleIndividualBoxChange.bind(this));
});
}

handleMainBoxChange (e) {
if (e.target.checked) {
this.deleteButton.removeAttribute("disabled");
this.deleteButton.disabled = false;
this.deleteButtonLabel.classList.remove('disabled');

this.individualBoxes.forEach((box) => {
box.setAttribute("checked", "true");
box.checked = true;
});
} else {
this.deleteButton.setAttribute("disabled", "disabled");
this.deleteButton.disabled = true;
this.deleteButtonLabel.classList.add('disabled');

this.individualBoxes.forEach((box) => {
box.removeAttribute("checked");
box.checked = false;
});
}
}

handleIndividualBoxChange (e) {
if (e.target.checked) {
this.deleteButton.removeAttribute("disabled");
this.deleteButton.disabled = false;
this.deleteButtonLabel.classList.remove('disabled');

const allChecked = Array.from(this.individualBoxes).every((box) => box.checked);
if (allChecked) {
this.selectAllBox.setAttribute("checked", "true");
this.selectAllBox.checked = true;
}
} else {
const boxes = Array.from(this.individualBoxes);
const anyUnchecked = boxes.some((box) => !box.checked);
const allUnchecked = boxes.every((box) => !box.checked);

if (anyUnchecked) {
this.selectAllBox.removeAttribute("checked");
this.selectAllBox.checked = false;
}

if (allUnchecked) {
this.deleteButton.setAttribute("disabled", "disabled");
this.deleteButton.disabled = true;
this.deleteButtonLabel.classList.add('disabled');
}
}
}
}

window.DeleteApiKeyBoxSet = DeleteApiKeyBoxSet;
}(window));

/**
* Attach all the listeners
*/
Expand Down Expand Up @@ -478,6 +556,11 @@ window.SortableTable = SortableTable;
new HiddenContent(hiddenContentCells[i]);
}

var deleteApiKeyBoxes = document.querySelectorAll('[data-apikeybox=select-all]');
for (var i = 0; i < deleteApiKeyBoxes.length; i++) {
new DeleteApiKeyBoxSet(deleteApiKeyBoxes[i]);
}

attachListenerToBillingFrequencySelector();
handleRunUpdate();
});
Expand Down Expand Up @@ -509,6 +592,12 @@ window.SortableTable = SortableTable;
for (var i = 0; i < hiddenContentCells.length; i++) {
new HiddenContent(hiddenContentCells[i]);
}

var deleteApiKeyBoxes = document.querySelectorAll('[data-apikeybox=select-all]');
for (var i = 0; i < deleteApiKeyBoxes.length; i++) {
new DeleteApiKeyBoxSet(deleteApiKeyBoxes[i]);
}

attachListenerToBillingFrequencySelector();
handleRunUpdate();
}
Expand Down
4 changes: 1 addition & 3 deletions www/src/CPClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,8 @@ public function createApiKey(string $name): array
}
}

public function deleteApiKey(int $id): array
public function deleteApiKey(array $ids): array
{
$ids = [$id];

$gql = (new Mutation('wptApiKeyBulkDelete'))
->setVariables([
new Variable('ids', '[Int!]', true)
Expand Down
29 changes: 29 additions & 0 deletions www/src/Handlers/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,33 @@ public static function cancelSubscription(RequestContext $request_context)
throw new ClientException("There was an error", "/account");
}
}

public static function deleteApiKey(RequestContext $request_context)
{
try {
$api_key_ids = $_POST['api-key-id'];
if (!empty($api_key_ids)) {
$sanitized_keys = array_filter($api_key_ids, function ($v) {
return filter_var($v, FILTER_SANITIZE_NUMBER_INT);
});
$ints = array_map(function ($v) {
return intval($v);
}, $sanitized_keys);

$request_context->getClient()->deleteApiKey($ints);
}


$protocol = $request_context->getUrlProtocol();
$host = Util::getSetting('host');
$route = '/account';
$redirect_uri = "{$protocol}://{$host}{$route}";

header("Location: {$redirect_uri}");
exit();
} catch (\Exception $e) {
error_log($e->getMessage());
throw new ClientException("There was an error", "/account");
}
}
}
116 changes: 58 additions & 58 deletions www/templates/account/includes/billing-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
<div class="create-key-container">
<div class="create-delete-button-container">
<button type="button" class="new-api-key" data-toggle="open" data-targetid="create-api-key-toggle-area">New Api Key</button>
<button type="button" class="delete-key" disabled>Delete</button>
<label for="delete-api-key-submit-input" class="delete-key disabled" data-apikeybox="delete-button" disabled>Delete</label>
</div>
<div class="toggleable" id="create-api-key-toggle-area">
<form method="POST" action="/account">
Expand All @@ -112,62 +112,62 @@
</div>
</div>
<div class="info">
<table class="sortable">
<caption>
<span class="sr-only">API Consumers table, column headers with buttons are sortable.</span>
</caption>
<thead>
<tr>
<th class="no-sort">Delete</th>
<th aria-sort="ascending">
<button>
Name
<span aria-hidden="true"></span>
</button>
</th>
<th>
<button>
API Key
<span aria-hidden="true"></span>
</button>
</th>
<th>
<button>
Create Date
<span aria-hidden="true"></span>
</button>
</th>
<th>
<button>
Last Updated
<span aria-hidden="true"></span>
</button>
</th>
</tr>
</thead>
<tbody>
<?php foreach($wptApiKey as $row): ?>
<tr>
<td>
<form method='POST' action='/account'>
<input type='hidden' name='api-key-id' value='<?= $row['id'] ?>' />
<input type='hidden' name='type' value='delete-api-key' />
<input type='hidden' name='csrf_token' value='<?= $csrf_token ?>' />
<button type='submit'>Delete</button>
</form>
</td>
<td><?= $row['name'] ?></td>
<td class="hidden-content">
<button type="button" class="view-button">View</button>
<span class="hidden-area closed">
<span class="api-key"><?= $row['apiKey'] ?></span>
<button type="button" class="hide-button"><span class="sr-only">Close</span></button></td>
</span>
<td><?= date_format(date_create($row['createDate']), 'M d Y H:i:s e') ?></td>
<td><?= date_format(date_create($row['changeDate']), 'M d Y H:i:s e') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<form method='POST' action='/account'>
<table class="sortable">
<caption>
<span class="sr-only">API Consumers table, column headers with buttons are sortable.</span>
</caption>
<thead>
<tr>
<th class="no-sort select-all-box"><label class="sr-only" for="select-all-api-keys">Select all api keys</label><input type="checkbox" name="select-all-api-keys" data-apikeybox="select-all" /></th>
<th aria-sort="ascending">
<button>
Name
<span aria-hidden="true"></span>
</button>
</th>
<th>
<button>
API Key
<span aria-hidden="true"></span>
</button>
</th>
<th>
<button>
Create Date
<span aria-hidden="true"></span>
</button>
</th>
<th>
<button>
Last Updated
<span aria-hidden="true"></span>
</button>
</th>
</tr>
</thead>
<tbody>
<?php foreach($wptApiKey as $row): ?>
<tr>
<td>
<input type='checkbox' data-apikeybox="individual" name='api-key-id[]' value='<?= $row['id'] ?>' />
</td>
<td><?= $row['name'] ?></td>
<td class="hidden-content">
<button type="button" class="view-button">View</button>
<span class="hidden-area closed">
<span class="api-key"><?= $row['apiKey'] ?></span>
<button type="button" class="hide-button"><span class="sr-only">Close</span></button></td>
</span>
<td><?= date_format(date_create($row['createDate']), 'M d Y H:i:s e') ?></td>
<td><?= date_format(date_create($row['changeDate']), 'M d Y H:i:s e') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<input type='hidden' name='type' value='delete-api-key' />
<input type='hidden' name='csrf_token' value='<?= $csrf_token ?>' />
<input type="submit" id="delete-api-key-submit-input" class="sr-only" />
</form>
</div>
</div>
4 changes: 2 additions & 2 deletions www/templates/account/my-account.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@

<?php if ($is_paid) {
include_once __DIR__ . '/includes/billing-data.php';
include_once __DIR__ . '/includes/modals/subscription-plan.php';
include_once __DIR__ . '/includes/modals/payment-info.php';
} else {
include_once __DIR__ . '/includes/signup.php';
} ?>
Expand All @@ -66,7 +68,5 @@
<?php
include_once __DIR__ . '/includes/modals/contact-info.php';
include_once __DIR__ . '/includes/modals/password.php';
include_once __DIR__ . '/includes/modals/subscription-plan.php';
include_once __DIR__ . '/includes/modals/payment-info.php';
?>
<!-- /Modals -->

0 comments on commit e6b26d1

Please sign in to comment.