Query supabase for checking invite deliveries

This commit is contained in:
David Ernst
2022-02-14 22:49:51 -08:00
parent 72233c46dd
commit 2a6c8be326
7 changed files with 77 additions and 20 deletions

View File

@@ -0,0 +1,34 @@
import { supabase } from 'api/_supabase'
import { buildSubject } from 'api/invite-voters'
import { checkJwtOwnsElection } from 'api/validate-admin-jwt'
import { NextApiRequest, NextApiResponse } from 'next'
export type VoterInvites = Record<string, string[]>
export default async (req: NextApiRequest, res: NextApiResponse) => {
const { election_id } = req.query as { election_id?: string }
if (!election_id) return res.status(401).json({ error: 'Missing election_id' })
// Confirm they're a valid admin that created this election
const jwt = await checkJwtOwnsElection(req, res, election_id)
if (!jwt.valid) return
const subject = buildSubject(jwt.election_title)
const headers = 'json->event-data->message->headers'
const toPath = headers + '->to'
const subjectPath = headers + '->subject'
const { data, error } = await supabase
.from('mailgun-deliveries')
.select(`${toPath}, created_at`)
.eq(subjectPath, JSON.stringify(subject))
if (error) return res.status(400).json({ error })
const deliveries = data?.reduce((memo, { created_at, to }) => {
memo[to] = [...(memo[to] || []), created_at]
return memo
}, {})
res.status(200).json(deliveries as VoterInvites)
}

View File

@@ -3,10 +3,12 @@ import { Tooltip } from './Tooltip'
export const DeliveriesAndFailures = ({
checkmarkOnly,
delivered,
deliveries,
failed,
}: {
checkmarkOnly?: boolean
delivered?: unknown[]
deliveries?: string[]
failed?: unknown[]
}) => {
return (
@@ -15,23 +17,22 @@ export const DeliveriesAndFailures = ({
interactive={!!failed}
placement="top"
title={
failed || delivered ? (
failed || deliveries ? (
<>
{(failed as {
'delivery-status': { message: string }
id: string
severity: string
}[])?.map((event) => (
{(
failed as {
'delivery-status': { message: string }
id: string
severity: string
}[]
)?.map((event) => (
<div key={event.id} style={{ fontSize: 14 }}>
<b>{event.severity} failure</b>: {event['delivery-status'].message.replace(/5.1.1 /g, '')}
</div>
))}
{(delivered as {
id: string
timestamp: number
}[])?.map((event) => (
<div key={event.id} style={{ fontSize: 14 }}>
{new Date(event.timestamp * 1000).toLocaleString()}
{deliveries?.map((time, index) => (
<div key={index} style={{ fontSize: 14 }}>
{new Date(time).toLocaleString()}
</div>
))}
</>
@@ -44,7 +45,7 @@ export const DeliveriesAndFailures = ({
<span className="failed-events">
{(failed as { severity?: string }[])?.filter((e) => e.severity === 'temporary').length ? '⚠️ ' : ''}
</span>
{checkmarkOnly ? (delivered ? '✓' : '') : delivered?.length}
{checkmarkOnly ? (delivered ? '✓' : '') : deliveries?.length}
<span className="failed-events">
{(failed as { severity?: string }[])?.filter(({ severity }) => severity === 'permanent').length ? ' X' : ''}
</span>

View File

@@ -6,11 +6,10 @@ import { NumVotedRow } from './NumVotedRow'
import { getStatus } from './Signature'
import { TopBarButtons } from './TopBarButtons'
import { UnlockedStatus } from './UnlockedStatus'
import { use_latest_mailgun_events } from './use-latest-mailgun-events'
import { ValidVotersTable } from './ValidVotersTables'
export const ExistingVoters = () => {
const { election_id, esignature_requested, valid_voters, voters } = useStored()
const { esignature_requested, valid_voters } = useStored()
const [checked, set_checked] = useState<boolean[]>(new Array(valid_voters?.length).fill(false))
const num_voted = valid_voters?.filter((v) => v.has_voted).length || 0
const num_approved = !esignature_requested
@@ -20,8 +19,6 @@ export const ExistingVoters = () => {
const [hide_voted, toggle_hide_voted] = useReducer((state) => !state, false)
const [hide_approved, toggle_hide_approved] = useReducer((state) => !state, false)
use_latest_mailgun_events(election_id, voters)
// Grow checked array to match voters list
useEffect(() => {
if (valid_voters && checked.length !== valid_voters.length) {

View File

@@ -9,6 +9,7 @@ import { mask } from './mask-token'
import { QueuedCell } from './QueuedCell'
import { Signature, getStatus } from './Signature'
import { use_multi_select } from './use-multi-select'
import { useVoterInvites } from './useVoterInvites'
export const ValidVotersTable = ({
checked,
@@ -27,6 +28,8 @@ export const ValidVotersTable = ({
const [mask_tokens, toggle_tokens] = useReducer((state) => !state, true)
const { last_selected, pressing_shift, set_last_selected } = use_multi_select()
const voterInvites = useVoterInvites()
if (!voters) return null
const shown_voters = voters.filter(
@@ -140,7 +143,7 @@ export const ValidVotersTable = ({
</td>
<QueuedCell {...{ invite_queued }} />
<DeliveriesAndFailures {...mailgun_events} />
<DeliveriesAndFailures {...mailgun_events} deliveries={voterInvites[email]} />
<td style={{ fontWeight: 700, textAlign: 'center' }}>{has_voted ? '✓' : ''}</td>

View File

@@ -0,0 +1,22 @@
import { VoterInvites } from 'api/election/[election_id]/admin/find-voter-invites'
import { useRouter } from 'next/router'
import useSWR, { mutate } from 'swr'
export function useVoterInvites(): VoterInvites {
const election_id = useRouter().query.election_id as string | undefined
const { data }: { data?: VoterInvites } = useSWR(election_id ? url(election_id) : null, (url: string) =>
fetch(url).then(async (r) => {
if (!r.ok) throw await r.json()
return await r.json()
}),
)
return { ...data }
}
export function recheckDeliveries(election_id?: string) {
mutate(url(election_id))
}
const url = (election_id?: string) => `${window.location.origin}/api/election/${election_id}/admin/find-voter-invites`

View File

@@ -1,8 +1,7 @@
import { useRouter } from 'next/router'
import { AdminData, Voter } from 'pages/api/election/[election_id]/admin/load-admin'
import useSWR, { mutate } from 'swr'
import { AdminData, Voter } from '../../pages/api/election/[election_id]/admin/load-admin'
export function useStored(): AdminData & { valid_voters?: Voter[] } {
const election_id = useRouter().query.election_id as string | undefined

View File

@@ -2,6 +2,7 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"api/*": ["./pages/api/*"],
"public/*": ["./public/*"],
"src/*": ["./src/*"]
},