Skip to content

Commit 37afe3d

Browse files
authored
Coommit includes the following: (#634)
- Titles of FASCA and Drone in all Trainings #616 - User Profile > Save Changes Without Returning the User to the Search Results Screen #621 - Bug: Admin User unable to download another user's certificate #623 - Dependabot Alert: Server-Side Request Forgery in axios #620
1 parent 8935f7b commit 37afe3d

File tree

17 files changed

+299
-131
lines changed

17 files changed

+299
-131
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ __pycache__
66
_site/
77
node_modules
88
.coverage
9+
.idea/workspace.xml

training-front-end/src/components/AdminEditReporting.vue

+15-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,19 @@ const editing = ref(false)
2020
// Copy to avoid modifying parent prop and allow cancelling edits
2121
const agencies = ref([...props.user.report_agencies])
2222
23-
const emit = defineEmits(['cancel', 'save', 'userUpdateSuccess'])
23+
watch(
24+
() => props.user,
25+
(updatedUser) => {
26+
if (updatedUser && updatedUser.report_agencies) {
27+
agencies.value = [...updatedUser.report_agencies];
28+
} else {
29+
agencies.value = [];
30+
}
31+
},
32+
{ deep: true } // Ensures that nested changes within the user object are detected
33+
);
34+
35+
const emit = defineEmits(['cancel', 'updateReportingAccess', 'userUpdateSuccess'])
2436
2537
const agency_options = useStore(agencyList)
2638
const bureaus = useStore(bureauList)
@@ -41,7 +53,7 @@ function editUserAgencies(e, checked) {
4153
})
4254
} else {
4355
agencies.value = agencies.value.filter(agency => agency.id != e.id)
44-
emit('save', props.user.id, agencies.value)
56+
emit('updateReportingAccess', props.user.id, agencies.value)
4557
}
4658
}
4759
@@ -268,7 +280,7 @@ function formatDate(dateStr) {
268280
<button
269281
id="update-user"
270282
class="usa-button usa-button--outline"
271-
@click="$emit('save', user.id, agencies)"
283+
@click="$emit('updateReportingAccess', user.id, agencies)"
272284
>
273285
Add Reporting Access
274286
</button>

training-front-end/src/components/AdminEditUserDetails.vue

+17-49
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import {useVuelidate} from "@vuelidate/core";
77
import ValidatedSelect from "./ValidatedSelect.vue";
88
import {useStore} from "@nanostores/vue";
99
import {agencyList, bureauList, setSelectedAgencyId} from "../stores/agencies.js";
10-
import {profile} from "../stores/user.js";
1110
import USWDSAlert from "./USWDSAlert.vue";
1211
import SpinnerGraphic from "./SpinnerGraphic.vue";
12+
import { RepositoryFactory } from "./RepositoryFactory.vue";
13+
const adminRepository = RepositoryFactory.get('admin')
1314
1415
const props = defineProps({
1516
userToEdit: {
1617
type: Object,
1718
required: true,
1819
}
1920
})
20-
const user = useStore(profile)
21-
const base_url = import.meta.env.PUBLIC_API_BASE_URL
21+
2222
const {withMessage} = helpers
2323
const agency_options = useStore(agencyList)
2424
const bureaus = useStore(bureauList)
@@ -62,8 +62,7 @@ onBeforeMount(async () => {
6262
})
6363
6464
async function update_user_info() {
65-
error.value = ref()
66-
show_error.value = false
65+
clearErrors()
6766
const isFormValid = await v_all_info$.value.$validate()
6867
6968
if (!isFormValid) {
@@ -72,60 +71,29 @@ async function update_user_info() {
7271
is_saving.value = true
7372
show_spinner.value = true
7473
75-
// When user has choosen a bureau use that id instead of the agency
74+
// When user has chosen a bureau use that id instead of the agency
7675
let {bureau_id, ...user_data} = user_input
7776
if (bureau_id) {
7877
user_data.agency_id = bureau_id
7978
}
8079
81-
const apiURL = new URL(`${base_url}/api/v1/users/${props.userToEdit.id}`)
82-
let response = ref();
83-
try {
84-
response.value = await fetch(apiURL, {
85-
method: 'PATCH',
86-
headers: {
87-
'Content-Type': 'application/json',
88-
'Authorization': `Bearer ${user.value.jwt}`
89-
},
90-
body: JSON.stringify(user_data)
91-
})
92-
} catch (error) {
93-
setError({
94-
name: 'Server Error',
95-
message: 'Sorry, we had an error connecting to the server.'
96-
})
80+
try{
81+
let updatedUser = await adminRepository.updateUser(props.userToEdit.id, user_data)
9782
is_saving.value = false
9883
show_spinner.value = false
99-
return
100-
}
101-
if (!response.value.ok) {
102-
is_saving.value = false
103-
show_spinner.value = false
104-
if (response.value.status === 400) {
105-
setError({
106-
name: 'Unauthorized',
107-
message: 'You are not authorized to edit profile.'
108-
})
109-
return
110-
}
111-
if (response.value.status === 403) {
112-
setError({
113-
name: 'Unauthorized',
114-
message: "You can not update your own profile."
115-
})
116-
return
117-
}
84+
let successMessage = `Successfully updated ${updatedUser.email}`
85+
emit('completeUserUpdate', successMessage)
86+
} catch(err){
11887
setError({
119-
name: 'Error',
120-
message: "Error contacting server."
88+
name: 'Response Error',
89+
message: err
12190
})
122-
return
12391
}
124-
is_saving.value = false
125-
show_spinner.value = false
126-
let updatedUser = await response.value.json()
127-
let successMessage = `Successfully updated ${updatedUser.email}`
128-
emit('completeUserUpdate', successMessage)
92+
}
93+
94+
function clearErrors(){
95+
error.value = ref()
96+
show_error.value = false
12997
}
13098
13199
function setError(event) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<script>
2+
import { profile} from '../stores/user'
3+
import { useStore } from '@nanostores/vue'
4+
import {ref} from "vue";
5+
6+
const user = useStore(profile)
7+
8+
const base_url = import.meta.env.PUBLIC_API_BASE_URL
9+
const users_api = `${base_url}/api/v1/users/`
10+
11+
const userSearch = async function(searchText, currentPage){
12+
const url = new URL(`${users_api}`)
13+
url.search = new URLSearchParams({searchText: searchText, page_number: currentPage + 1})
14+
15+
const response = await fetch(
16+
url, {
17+
method: 'GET',
18+
headers: {
19+
'Authorization': `Bearer ${user.value.jwt}`
20+
}
21+
}
22+
)
23+
if (! response.ok) {
24+
const message = await response.text()
25+
throw new Error(message)
26+
}
27+
return await response.json()
28+
}
29+
30+
const updateUserReports = async function(userId, agencyIds) {
31+
const agencies = agencyIds.map(a => a.id)
32+
const url = new URL(`${users_api}edit-user-for-reporting/`)
33+
url.search = new URLSearchParams({user_id: userId})
34+
35+
const response = await fetch(
36+
url, {
37+
method: "PATCH",
38+
headers: {
39+
'Content-Type': 'application/json',
40+
'Authorization': `Bearer ${user.value.jwt}`
41+
},
42+
body: JSON.stringify(agencies)
43+
}
44+
)
45+
if (!response.ok) {
46+
const message = await response.text()
47+
throw new Error(message)
48+
}
49+
return await response.json()
50+
}
51+
52+
53+
const getUser = async function(userId){
54+
const url = new URL(`${users_api}${userId}`)
55+
56+
const response = await fetch(
57+
url, {
58+
method: 'GET',
59+
headers: {
60+
'Authorization': `Bearer ${user.value.jwt}`
61+
}
62+
}
63+
)
64+
if (! response.ok) {
65+
const message = await response.text()
66+
throw new Error(message)
67+
}
68+
return await response.json()
69+
}
70+
71+
const updateUser = async function(userId, userData){
72+
const apiURL = new URL(`${users_api}${userId}`)
73+
let response = ref();
74+
try {
75+
response.value = await fetch(apiURL, {
76+
method: 'PATCH',
77+
headers: {
78+
'Content-Type': 'application/json',
79+
'Authorization': `Bearer ${user.value.jwt}`
80+
},
81+
body: JSON.stringify(userData)
82+
})
83+
} catch (error) {
84+
throw new Error('Sorry, we had an error connecting to the server.')
85+
}
86+
87+
if (!response.value.ok) {
88+
if (response.value.status === 400) {
89+
throw new Error('You are not authorized to edit profile.')
90+
}
91+
if (response.value.status === 403) {
92+
throw new Error("You can not update your own profile.")
93+
}
94+
throw new Error("Error contacting server.")
95+
}
96+
return await response.value.json()
97+
}
98+
99+
export default {
100+
userSearch,
101+
updateUserReports,
102+
getUser,
103+
updateUser,
104+
}
105+
106+
</script>

training-front-end/src/components/AdminSearchUser.vue

+23-53
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@
55
import USWDSPagination from "./USWDSPagination.vue";
66
import USWDSAlert from './USWDSAlert.vue'
77
import { setSelectedAgencyId} from '../stores/agencies'
8-
import { useStore } from '@nanostores/vue'
9-
import { profile} from '../stores/user'
10-
11-
const user = useStore(profile)
8+
import { RepositoryFactory } from "./RepositoryFactory.vue";
9+
const adminRepository = RepositoryFactory.get('admin')
1210
1311
const PAGE_SIZE = 25
1412
15-
const base_url = import.meta.env.PUBLIC_API_BASE_URL
16-
const report_url = `${base_url}/api/v1/users/`
17-
const update_url = `${base_url}/api/v1/users/edit-user-for-reporting/`
18-
1913
let currentPage = ref(0)
2014
let numberOfResults = ref(0)
2115
const numberOfPages = computed(() => Math.ceil(numberOfResults.value/PAGE_SIZE))
@@ -36,23 +30,8 @@
3630
async function search() {
3731
clearAlerts()
3832
noResults.value = false
39-
const url = new URL(`${report_url}`)
40-
url.search = new URLSearchParams({searchText: searchTerm.value, page_number: currentPage.value + 1})
41-
4233
try {
43-
const response = await fetch(
44-
url, {
45-
method: 'GET',
46-
headers: {
47-
'Authorization': `Bearer ${user.value.jwt}`
48-
}
49-
}
50-
)
51-
if (! response.ok) {
52-
const message = await response.text()
53-
throw new Error(message)
54-
}
55-
let search_results = await response.json()
34+
let search_results = await adminRepository.userSearch(searchTerm.value, currentPage.value)
5635
searchResults.value = search_results.users
5736
numberOfResults.value = search_results.total_count
5837
noResults.value = search_results.total_count === 0
@@ -62,50 +41,41 @@
6241
}
6342
6443
async function updateUserReports(userId, agencyIds) {
65-
const agencies = agencyIds.map(a => a.id)
66-
const url = new URL(update_url)
67-
url.search = new URLSearchParams({user_id: userId})
6844
try {
69-
const response = await fetch(
70-
url, {
71-
method: "PATCH",
72-
headers: {
73-
'Content-Type': 'application/json',
74-
'Authorization': `Bearer ${user.value.jwt}`
75-
},
76-
body: JSON.stringify(agencies)
77-
}
78-
)
79-
if (!response.ok) {
80-
const message = await response.text()
81-
throw new Error(message)
82-
}
83-
let updatedUser = await response.json()
45+
let updatedUser = await adminRepository.updateUserReports(userId, agencyIds)
8446
selectedUser.value.report_agencies = updatedUser.report_agencies
85-
setCurrentUser(undefined)
86-
setSelectedAgencyId(undefined)
47+
updateUserSuccess("Updated users reporting access")
48+
refreshSelectedUser()
8749
} catch (err){
8850
error.value = err
8951
}
9052
}
9153
92-
function setCurrentUser(e) {
54+
async function refreshSelectedUser(){
55+
if(!selectedUser.value){
56+
return
57+
}
58+
59+
try{
60+
selectedUser.value = await adminRepository.getUser(selectedUser.value.id)
61+
} catch (err) {
62+
error.value = err
63+
}
64+
}
65+
66+
function setSelectedUser(e) {
9367
selectedUser.value = e
9468
}
9569
9670
function cancelEdit(){
97-
setCurrentUser(undefined)
71+
setSelectedUser(undefined)
9872
setSelectedAgencyId(undefined)
9973
}
10074
10175
async function updateUserSuccess(message) {
76+
refreshSelectedUser()
10277
successMessage.value = message
10378
showSuccessMessage.value = true
104-
cancelEdit()
105-
currentPage.value = 0
106-
numberOfResults.value = 0
107-
searchTerm.value = ''
108-
searchResults.value = []
10979
}
11080
11181
function clearAlerts() {
@@ -182,7 +152,7 @@
182152
<AdminUserSearchTable
183153
:number-of-results="numberOfResults"
184154
:search-results="searchResults"
185-
@select-item="setCurrentUser"
155+
@select-item="setSelectedUser"
186156
/>
187157
<USWDSPagination
188158
:current-page="currentPage"
@@ -200,7 +170,7 @@
200170
<div v-else>
201171
<AdminEditReporting
202172
:user="selectedUser"
203-
@save="updateUserReports"
173+
@update-reporting-access="updateUserReports"
204174
@cancel="cancelEdit"
205175
@user-update-success="updateUserSuccess"
206176
/>

0 commit comments

Comments
 (0)