Skip to content

Commit 2687d37

Browse files
Disable/Enable Routes Should Use ID (Fixes #932) (#933)
* Add some basic table searching * Update /enable/ and /disable/ to use id instead of email * Clean up unused imports * Use case insensitive matching for filters
1 parent 84199c8 commit 2687d37

File tree

5 files changed

+146
-84
lines changed

5 files changed

+146
-84
lines changed

backend/src/appointment/routes/subscriber.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ def get_all_subscriber(
4747
)
4848

4949

50-
@router.put('/disable/{email}')
50+
@router.put('/disable/{id}')
5151
def disable_subscriber(
52-
email: str, db: Session = Depends(get_db), subscriber: Subscriber = Depends(get_admin_subscriber)
52+
id: str, db: Session = Depends(get_db), subscriber: Subscriber = Depends(get_admin_subscriber)
5353
):
54-
"""endpoint to mark a subscriber deleted by email, needs admin permissions"""
55-
subscriber_to_delete = repo.subscriber.get_by_email(db, email)
54+
"""endpoint to mark a subscriber deleted by id, needs admin permissions"""
55+
subscriber_to_delete = repo.subscriber.get(db, int(id))
5656
if not subscriber_to_delete:
5757
raise validation.SubscriberNotFoundException()
5858
if subscriber_to_delete.is_deleted:
@@ -64,10 +64,10 @@ def disable_subscriber(
6464
return repo.subscriber.disable(db, subscriber_to_delete)
6565

6666

67-
@router.put('/enable/{email}')
68-
def enable_subscriber(email: str, db: Session = Depends(get_db), _: Subscriber = Depends(get_admin_subscriber)):
69-
"""endpoint to enable a subscriber by email, needs admin permissions"""
70-
subscriber_to_enable = repo.subscriber.get_by_email(db, email)
67+
@router.put('/enable/{id}')
68+
def enable_subscriber(id: str, db: Session = Depends(get_db), _: Subscriber = Depends(get_admin_subscriber)):
69+
"""endpoint to enable a subscriber by id, needs admin permissions"""
70+
subscriber_to_enable = repo.subscriber.get(db, int(id))
7171
if not subscriber_to_enable:
7272
raise validation.SubscriberNotFoundException()
7373
if not subscriber_to_enable.is_deleted:

backend/test/integration/test_subscriber.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def test_disable_enable_subscriber(self, with_client, make_basic_subscriber):
189189
assert new_subscriber.time_deleted is None
190190

191191
# disable our new subscriber and verify
192-
response = with_client.put(f'/subscriber/disable/{new_subscriber.email}', headers=auth_headers)
192+
response = with_client.put(f'/subscriber/disable/{new_subscriber.id}', headers=auth_headers)
193193

194194
assert response.status_code == 200, response.text
195195
response = with_client.post('/subscriber', json={'page': 1}, headers=auth_headers)
@@ -204,14 +204,14 @@ def test_disable_enable_subscriber(self, with_client, make_basic_subscriber):
204204
assert date_deleted == today
205205

206206
# attempt to disable same subscriber again, expect fail
207-
response = with_client.put(f'/subscriber/disable/{new_subscriber.email}', headers=auth_headers)
207+
response = with_client.put(f'/subscriber/disable/{new_subscriber.id}', headers=auth_headers)
208208

209209
assert response.status_code == 400, response.text
210210
data = response.json()
211211
assert data['detail']['id'] == 'SUBSCRIBER_ALREADY_DELETED'
212212

213213
# now enable the deleted subscriber and verify
214-
response = with_client.put(f'/subscriber/enable/{new_subscriber.email}', headers=auth_headers)
214+
response = with_client.put(f'/subscriber/enable/{new_subscriber.id}', headers=auth_headers)
215215

216216
assert response.status_code == 200, response.text
217217
response = with_client.post('/subscriber', json={'page': 1}, headers=auth_headers)
@@ -222,7 +222,7 @@ def test_disable_enable_subscriber(self, with_client, make_basic_subscriber):
222222
assert subscriber_ret['time_deleted'] is None
223223

224224
# attempt to enable the same subscriber again, expect fail
225-
response = with_client.put(f'/subscriber/enable/{new_subscriber.email}', headers=auth_headers)
225+
response = with_client.put(f'/subscriber/enable/{new_subscriber.id}', headers=auth_headers)
226226

227227
assert response.status_code == 400, response.text
228228
data = response.json()
@@ -233,7 +233,7 @@ def test_disable_subscriber_self_delete_failure(self, with_client):
233233
os.environ['APP_ADMIN_ALLOW_LIST'] = '@example.org'
234234

235235
# disable our current subscriber and verify
236-
response = with_client.put(f'/subscriber/disable/{os.getenv('TEST_USER_EMAIL')}', headers=auth_headers)
236+
response = with_client.put(f'/subscriber/disable/{TEST_USER_ID}', headers=auth_headers)
237237

238238
assert response.status_code == 403, response.text
239239
data = response.json()
@@ -244,7 +244,7 @@ def test_disable_subscriber_not_found(self, with_client, make_basic_subscriber):
244244
os.environ['APP_ADMIN_ALLOW_LIST'] = '@example.org'
245245

246246
# disable a subscriber that doesn't exist
247-
response = with_client.put('/subscriber/disable/[email protected]', headers=auth_headers)
247+
response = with_client.put('/subscriber/disable/999999999999', headers=auth_headers)
248248

249249
assert response.status_code == 404, response.text
250250
data = response.json()
@@ -254,7 +254,7 @@ def test_disable_subscriber_no_admin(self, with_client):
254254
# ensure our current subscriber is not admin
255255
os.environ['APP_ADMIN_ALLOW_LIST'] = '@notexample.org'
256256

257-
response = with_client.put(f'/subscriber/disable/{os.getenv('TEST_USER_EMAIL')}', headers=auth_headers)
257+
response = with_client.put(f'/subscriber/disable/{TEST_USER_ID}', headers=auth_headers)
258258

259259
assert response.status_code == 401, response.text
260260
data = response.json()
@@ -265,7 +265,7 @@ def test_enable_subscriber_not_found(self, with_client, make_basic_subscriber):
265265
os.environ['APP_ADMIN_ALLOW_LIST'] = '@example.org'
266266

267267
# disable a subscriber that doesn't exist
268-
response = with_client.put('/subscriber/enable/[email protected]', headers=auth_headers)
268+
response = with_client.put('/subscriber/enable/8888888888888888888', headers=auth_headers)
269269

270270
assert response.status_code == 404, response.text
271271
data = response.json()
@@ -275,7 +275,7 @@ def test_enable_subscriber_no_admin(self, with_client):
275275
# ensure our current subscriber is not admin
276276
os.environ['APP_ADMIN_ALLOW_LIST'] = '@notexample.org'
277277

278-
response = with_client.put(f'/subscriber/enable/{os.getenv('TEST_USER_EMAIL')}', headers=auth_headers)
278+
response = with_client.put(f'/subscriber/enable/{TEST_USER_ID}', headers=auth_headers)
279279

280280
assert response.status_code == 401, response.text
281281
data = response.json()
@@ -324,7 +324,7 @@ def test_hard_delete_subscriber(
324324
steves_schedule = make_schedule(calendar_id=steves_calendar.id)
325325

326326
# disable steves account
327-
response = with_client.put(f'/subscriber/disable/{steve.email}', headers=auth_headers)
327+
response = with_client.put(f'/subscriber/disable/{steve.id}', headers=auth_headers)
328328

329329
assert response.status_code == 200, response.json()
330330

frontend/src/views/admin/InviteCodePanelView.vue

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ import {
33
computed, inject, onMounted, ref,
44
} from 'vue';
55
import { useI18n } from 'vue-i18n';
6-
import {
7-
AlertSchemes, TableDataButtonType, TableDataType, InviteStatus,
8-
} from '@/definitions';
96
import { useRouter } from 'vue-router';
107
import { IconSend } from '@tabler/icons-vue';
11-
import {
12-
Invite, InviteListResponse, BooleanResponse, Exception, TableDataRow, TableDataColumn, TableFilter, Alert,
13-
} from '@/models';
14-
import { dayjsKey, callKey } from '@/keys';
158
import DataTable from '@/components/DataTable.vue';
169
import LoadingSpinner from '@/elements/LoadingSpinner.vue';
1710
import PrimaryButton from '@/elements/PrimaryButton.vue';
1811
import AlertBox from '@/elements/AlertBox.vue';
1912
import AdminNav from '@/elements/admin/AdminNav.vue';
13+
import { dayjsKey, callKey } from '@/keys';
14+
import {
15+
Invite, InviteListResponse, BooleanResponse, Exception, TableDataRow, TableDataColumn, TableFilter, Alert,
16+
} from '@/models';
17+
import {
18+
AlertSchemes, TableDataButtonType, TableDataType, InviteStatus,
19+
} from '@/definitions';
2020
import { staggerRetrieve } from '@/utils';
2121
2222
const router = useRouter();
@@ -31,8 +31,15 @@ const generateCodeAmount = ref(null);
3131
const loading = ref(true);
3232
const pageError = ref<Alert>(null);
3333
const pageNotification = ref<Alert>(null);
34+
const codeFilter = ref<string>(null);
35+
const inviteList = computed(() => {
36+
if (codeFilter.value) {
37+
return invites.value.filter((invite) => invite.code.match(new RegExp(codeFilter.value, 'gi')));
38+
}
39+
return invites.value;
40+
});
3441
35-
const filteredInvites = computed(() => invites.value.map((invite) => ({
42+
const filteredInvites = computed(() => inviteList.value.map((invite) => ({
3643
code: {
3744
type: TableDataType.Code,
3845
value: invite.code,
@@ -148,7 +155,7 @@ const getInvites = async () => {
148155
149156
invites.value = await staggerRetrieve(
150157
(payload: object) => call('invite/').post(payload).json(),
151-
50,
158+
250,
152159
pageError,
153160
);
154161
};
@@ -253,27 +260,41 @@ onMounted(async () => {
253260
@field-click="(_key, field) => revokeInvite(field.code.value)"
254261
>
255262
<template v-slot:footer>
256-
<div class="flex w-1/3 flex-col gap-4 text-center md:w-full md:flex-row md:text-left">
257-
<label class="flex flex-col gap-4 md:flex-row md:items-center md:gap-0">
258-
<span>{{ t('label.amountOfCodes') }}</span>
259-
<input
260-
class="mx-4 w-60 rounded-md text-sm"
261-
type="number"
262-
v-model="generateCodeAmount"
263+
<div class="flex w-full justify-between">
264+
<div class="flex w-1/3 flex-col gap-4 text-center md:w-full md:flex-row md:text-left">
265+
<label class="flex flex-col gap-4 md:flex-row md:items-center md:gap-0">
266+
<span>{{ t('label.amountOfCodes') }}</span>
267+
<input
268+
class="mx-4 w-60 rounded-md text-sm"
269+
type="number"
270+
v-model="generateCodeAmount"
271+
:disabled="loading"
272+
enterkeyhint="done"
273+
@keyup.enter="generateInvites"
274+
/>
275+
</label>
276+
<primary-button
277+
class="btn-generate"
263278
:disabled="loading"
264-
enterkeyhint="done"
265-
@keyup.enter="generateInvites"
266-
/>
267-
</label>
268-
<primary-button
269-
class="btn-generate"
270-
:disabled="loading"
271-
@click="generateInvites"
272-
:title="t('label.generate')"
273-
>
274-
<icon-send/>
275-
{{ t('label.generate') }}
276-
</primary-button>
279+
@click="generateInvites"
280+
:title="t('label.generate')"
281+
>
282+
<icon-send/>
283+
{{ t('label.generate') }}
284+
</primary-button>
285+
</div>
286+
<div class="flex flex-col items-end gap-4">
287+
<label class="flex flex-col gap-4 md:flex-row md:items-center md:gap-0">
288+
<span>{{ t('label.search') }}</span>
289+
<input
290+
class="mx-4 w-60 rounded-md text-sm"
291+
type="text"
292+
v-model="codeFilter"
293+
:disabled="loading"
294+
enterkeyhint="search"
295+
/>
296+
</label>
297+
</div>
277298
</div>
278299
</template>
279300

frontend/src/views/admin/SubscriberPanelView.vue

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
<script setup lang="ts">
2-
import { AlertSchemes, TableDataButtonType, TableDataType } from '@/definitions';
32
import {
43
computed, inject, onMounted, ref,
54
} from 'vue';
65
import { IconSend } from '@tabler/icons-vue';
76
import { useI18n } from 'vue-i18n';
87
import { useRouter } from 'vue-router';
9-
import { useUserStore } from '@/stores/user-store';
10-
import {
11-
Subscriber, SubscriberListResponse, BooleanResponse, Exception, TableDataRow, TableDataColumn, TableFilter, Alert,
12-
} from '@/models';
13-
import { dayjsKey, callKey } from '@/keys';
148
import AdminNav from '@/elements/admin/AdminNav.vue';
159
import AlertBox from '@/elements/AlertBox.vue';
1610
import DataTable from '@/components/DataTable.vue';
1711
import LoadingSpinner from '@/elements/LoadingSpinner.vue';
1812
import PrimaryButton from '@/elements/PrimaryButton.vue';
1913
import ConfirmationModal from '@/components/ConfirmationModal.vue';
20-
import { sleep, staggerRetrieve } from '@/utils';
14+
import { dayjsKey, callKey } from '@/keys';
15+
import {
16+
Subscriber, BooleanResponse, Exception, TableDataRow, TableDataColumn, TableFilter, Alert,
17+
} from '@/models';
18+
import { useUserStore } from '@/stores/user-store';
19+
import { AlertSchemes, TableDataButtonType, TableDataType } from '@/definitions';
20+
import { staggerRetrieve } from '@/utils';
2121
2222
const user = useUserStore();
2323
@@ -35,8 +35,15 @@ const pageError = ref<Alert>(null);
3535
const pageNotification = ref<Alert>(null);
3636
const hardDeleteModalOpen = ref<boolean>(false);
3737
const hardDeleteModalContext = ref<Subscriber>(null);
38+
const emailFilter = ref<string>(null);
39+
const subscriberList = computed(() => {
40+
if (emailFilter.value) {
41+
return subscribers.value.filter((sub) => sub.email.match(new RegExp(emailFilter.value, 'gi')));
42+
}
43+
return subscribers.value;
44+
});
3845
39-
const filteredSubscribers = computed(() => subscribers.value.map((subscriber) => ({
46+
const filteredSubscribers = computed(() => subscriberList.value.map((subscriber) => ({
4047
id: {
4148
type: TableDataType.Text,
4249
value: subscriber.id,
@@ -154,7 +161,7 @@ const getSubscribers = async () => {
154161
155162
subscribers.value = await staggerRetrieve(
156163
(payload: object) => call('subscriber/').post(payload).json(),
157-
50,
164+
250,
158165
pageError,
159166
);
160167
};
@@ -294,23 +301,37 @@ onMounted(async () => {
294301
@field-click="onFieldClick"
295302
>
296303
<template v-slot:footer>
297-
<div class="flex w-1/3 flex-col gap-4 text-center md:w-full md:flex-row md:text-left">
298-
<label class="flex flex-col gap-4 md:flex-row md:items-center md:gap-0">
299-
<span>{{ t('label.enterEmailToInvite') }}</span>
300-
<input
301-
class="mx-4 w-60 rounded-md text-sm"
302-
type="email"
303-
placeholder="e.g. [email protected]"
304-
v-model="inviteEmail"
305-
:disabled="loading"
306-
enterkeyhint="send"
307-
@keyup.enter="sendInvite"
308-
/>
309-
</label>
310-
<primary-button class="btn-send" :disabled="loading" @click="sendInvite" :title="t('label.send')">
311-
<icon-send />
312-
{{ t('label.send') }}
313-
</primary-button>
304+
<div class="flex w-full justify-between">
305+
<div class="flex w-1/3 flex-col gap-4 text-center md:w-full md:flex-row md:text-left">
306+
<label class="flex flex-col gap-4 md:flex-row md:items-center md:gap-0">
307+
<span>{{ t('label.enterEmailToInvite') }}</span>
308+
<input
309+
class="mx-4 w-60 rounded-md text-sm"
310+
type="email"
311+
placeholder="e.g. [email protected]"
312+
v-model="inviteEmail"
313+
:disabled="loading"
314+
enterkeyhint="send"
315+
@keyup.enter="sendInvite"
316+
/>
317+
</label>
318+
<primary-button class="btn-send" :disabled="loading" @click="sendInvite" :title="t('label.send')">
319+
<icon-send/>
320+
{{ t('label.send') }}
321+
</primary-button>
322+
</div>
323+
<div class="flex flex-col items-end gap-4">
324+
<label class="flex flex-col gap-4 md:flex-row md:items-center md:gap-0">
325+
<span>{{ t('label.search') }}</span>
326+
<input
327+
class="mx-4 w-60 rounded-md text-sm"
328+
type="text"
329+
v-model="emailFilter"
330+
:disabled="loading"
331+
enterkeyhint="search"
332+
/>
333+
</label>
334+
</div>
314335
</div>
315336
</template>
316337

0 commit comments

Comments
 (0)