-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue #456 GSPC | Email Title and Content Once User Initially Attempts to Begin GSPC Verification Issue #419 Administrator | Loading GSPC Eligible List into the Training System Issue #494 Create Training - Admin Landing Page Issue #496 Dependabot Alert: FastAPI Content-Type Header ReDoS
- Loading branch information
Showing
21 changed files
with
869 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"compounds": [ | ||
{ | ||
"name": "Client+Server", | ||
"configurations": [ "Python Debugger", "Astro" ] | ||
} | ||
], | ||
"configurations": [ | ||
{ | ||
"name": "Python Debugger", | ||
"type": "debugpy", | ||
"request": "launch", | ||
"module": "uvicorn", | ||
"args": ["training.main:app", "--reload"], | ||
"jinja": true, | ||
"cwd": "${workspaceFolder}/training", | ||
"console": "integratedTerminal", | ||
"env": {"PYTHONPATH" : "${workspaceRoot}"}, | ||
}, | ||
{ | ||
"command": "npm run dev:frontend", | ||
"name": "Astro", | ||
"request": "launch", | ||
"type": "node-terminal" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"""add gspc invite table | ||
Revision ID: 51b251b1ec2a | ||
Revises: 3acf0ea1ba59 | ||
Create Date: 2024-02-27 13:41:26.028121 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = '51b251b1ec2a' | ||
down_revision = '3acf0ea1ba59' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade() -> None: | ||
op.create_table( | ||
'gspc_invite', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('email', sa.String(), nullable=False), | ||
sa.Column('created_date', sa.DateTime, nullable=False, server_default=sa.func.current_timestamp()), | ||
sa.Column('certification_expiration_date', sa.Date, nullable=False), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
|
||
def downgrade() -> None: | ||
op.drop_table('gspc_invite') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
<script setup> | ||
import { ref, reactive } from 'vue'; | ||
import USWDSAlert from './USWDSAlert.vue' | ||
import ValidatedTextArea from './ValidatedTextArea.vue'; | ||
import ValidatedDatePicker from './ValidatedDatepicker.vue'; | ||
import { useVuelidate } from '@vuelidate/core'; | ||
import { required, helpers } from '@vuelidate/validators'; | ||
import SpinnerGraphic from './SpinnerGraphic.vue' | ||
import { useStore } from '@nanostores/vue' | ||
import { profile} from '../stores/user' | ||
const user = useStore(profile) | ||
const { withMessage } = helpers | ||
const base_url = import.meta.env.PUBLIC_API_BASE_URL | ||
const showSuccessMessage = ref(false) | ||
const showFailedMessage = ref(false) | ||
const failedEmailList = ref('') | ||
const successCount = ref(0) | ||
const isLoading = ref(false) | ||
const showSpinner = ref(false) | ||
const emit = defineEmits(['startLoading', 'endLoading', 'error']) | ||
const user_input = reactive({ | ||
emailAddresses: undefined, | ||
certificationExpirationDate: undefined | ||
}) | ||
const isNotPast = helpers.withParams( | ||
{ type: 'notPast' }, | ||
(value) => { | ||
const currentDate = new Date(); | ||
const inputDate = new Date(value); | ||
return inputDate >= currentDate; | ||
} | ||
); | ||
/* Form validation for additional information if we allow registation here */ | ||
const validations_all_info = { | ||
emailAddresses: { | ||
required: withMessage('Please enter the email addresses to invite', required) | ||
}, | ||
certificationExpirationDate: { | ||
required: withMessage('Please enter the cerification experation date', required), | ||
isNotPast: withMessage('The date must not be in the past', isNotPast) | ||
}, | ||
} | ||
const v_all_info$ = useVuelidate(validations_all_info, user_input) | ||
async function submitGspcInvites(){ | ||
const validation = v_all_info$ | ||
const isFormValid = await validation.value.$validate() | ||
if (!isFormValid) { | ||
showSpinner.value = false; | ||
return | ||
} | ||
emit('startLoading') | ||
isLoading.value = true | ||
showSpinner.value = true | ||
const apiURL = new URL(`${base_url}/api/v1/gspc-invite`) | ||
try { | ||
let res = await fetch(apiURL, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json','Authorization': `Bearer ${user.value.jwt}` }, | ||
body: JSON.stringify({ | ||
email_addresses: user_input.emailAddresses, | ||
certification_expiration_date: user_input.certificationExpirationDate | ||
}) | ||
}); | ||
if (!res.ok) { | ||
if (res.status == 401) { | ||
throw new Error("Unauthorized") | ||
} | ||
throw new Error("Error contacting server") | ||
} | ||
if (res.status == 200) { | ||
const data = await res.json(); | ||
if (data.valid_emails.length > 0) { | ||
showSuccessMessage.value = true; | ||
successCount.value = data.valid_emails.length; | ||
} else{ | ||
showSuccessMessage.value = false; | ||
} | ||
if (data.invalid_emails.length > 0) { | ||
showFailedMessage.value = true | ||
failedEmailList.value = data.invalid_emails.join(', '); | ||
} else{ | ||
showFailedMessage.value = false | ||
} | ||
} | ||
isLoading.value = false | ||
showSpinner.value = false | ||
emit('endLoading') | ||
} catch (err) { | ||
isLoading.value = false | ||
showSpinner.value = false | ||
const e = new Error("Sorry, we had an error connecting to the server.") | ||
e.name = "Server Error" | ||
emit('endLoading') | ||
throw e | ||
} | ||
} | ||
</script> | ||
<template> | ||
<div class="padding-top-4 padding-bottom-4 grid-container"> | ||
<h2>Send Invitations for GSA SmartPay Program Certification</h2> | ||
<p> | ||
After the attendees finish the necessary coursework for the GSA SmartPay Program Certification (GSPC), as stated in Smart Bulletin 22 during the GSA SmartPay Training Forum, you can use the form below to send each attendee an email containing a link. This link will enable them to certify their hands-on experience and obtain a PDF copy of their GSPC. | ||
</p> | ||
<p> | ||
Please fill out the form below by entering the attendees' email addresses as a comma-separated list and selecting an expiration date for their certificate. The expiration date should be three years from the date of the GSA SmartPay Training Forum. This form will verify the entered email addresses and notify you if any are invalid. | ||
</p> | ||
<form | ||
class="usa-form usa-form--large margin-bottom-3 " | ||
data-test="gspc-form" | ||
@submit.prevent="submitGspcInvites" | ||
> | ||
<ValidatedTextArea | ||
v-model="user_input.emailAddresses" | ||
client:load | ||
:validator="v_all_info$.emailAddresses" | ||
label="Email Addresses of GSA SmartPay Forum Attendees" | ||
name="email-list" | ||
/> | ||
<ValidatedDatePicker | ||
v-model="user_input.certificationExpirationDate" | ||
client:load | ||
:validator="v_all_info$.certificationExpirationDate" | ||
label="Certification Expiration Date" | ||
name="certification-expiration-date" | ||
hint-text="For example: January 19 2000" | ||
/> | ||
<div> | ||
<USWDSAlert | ||
v-if="showFailedMessage" | ||
status="error" | ||
class="usa-alert--slim" | ||
:has-heading="false" | ||
> | ||
Emails failed to send to: {{ failedEmailList }} | ||
</USWDSAlert> | ||
<USWDSAlert | ||
v-if="showSuccessMessage" | ||
status="success" | ||
class="usa-alert--slim" | ||
:has-heading="false" | ||
> | ||
Emails successfully sent to {{ successCount }} people. | ||
</USWDSAlert> | ||
</div> | ||
<div class="grid-row"> | ||
<div class="grid-col tablet:grid-col-3 "> | ||
<input | ||
class="usa-button" | ||
type="submit" | ||
value="Submit" | ||
:disabled="isLoading" | ||
data-test="submit" | ||
> | ||
</div> | ||
<!--display spinner along with submit button in one row for desktop--> | ||
<div | ||
v-if="showSpinner" | ||
class="display-none tablet:display-block tablet:grid-col-1 tablet:padding-top-3 tablet:margin-left-neg-1" | ||
> | ||
<SpinnerGraphic /> | ||
</div> | ||
</div> | ||
<!--display spinner under submit button for mobile view--> | ||
<div | ||
v-if="showSpinner" | ||
class="tablet:display-none margin-top-1 text-center" | ||
> | ||
<SpinnerGraphic /> | ||
</div> | ||
</form> | ||
</div> | ||
</template> | ||
<style> | ||
.usa-textarea { | ||
height: 15rem; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.