Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/customize mailing address #104

Merged
merged 5 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions apps/cacvote-jx-terminal/backend/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::Duration;

use auth_rs::card_details::CardDetailsWithAuthInfo;
use axum::body::Bytes;
use axum::response::sse::{Event, KeepAlive};
use axum::response::Sse;
use axum::routing::post;
Expand All @@ -25,7 +24,6 @@ use tracing::Level;
use types_rs::cacvote::{
CreateRegistrationData, Election, Payload, Registration, SessionData, SignedObject,
};
use types_rs::election::ElectionDefinition;
use uuid::Uuid;

use crate::config::{Config, MAX_REQUEST_SIZE};
Expand Down Expand Up @@ -185,7 +183,7 @@ async fn create_election(
State(AppState {
pool, smartcard, ..
}): State<AppState>,
body: Bytes,
Json(election): Json<Election>,
) -> impl IntoResponse {
let jurisdiction_code = match smartcard.get_card_details() {
Some(card_details) => card_details.card_details.jurisdiction_code(),
Expand All @@ -198,16 +196,12 @@ async fn create_election(
}
};

let election_definition = match ElectionDefinition::try_from(&body[..]) {
Ok(election_definition) => election_definition,
Err(e) => {
tracing::error!("error parsing election definition: {e}");
return (
StatusCode::BAD_REQUEST,
Json(json!({ "error": format!("error parsing election definition: {e}") })),
);
}
};
if election.jurisdiction_code != jurisdiction_code {
return (
StatusCode::BAD_REQUEST,
Json(json!({ "error": "jurisdiction_code does not match card details" })),
);
}

let mut connection = match pool.acquire().await {
Ok(connection) => connection,
Expand All @@ -220,10 +214,7 @@ async fn create_election(
}
};

let payload = Payload::Election(Election {
jurisdiction_code,
election_definition,
});
let payload = Payload::Election(election);
let serialized_payload = match serde_json::to_vec(&payload) {
Ok(serialized_payload) => serialized_payload,
Err(e) => {
Expand Down
1 change: 1 addition & 0 deletions apps/cacvote-jx-terminal/backend/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ mod tests {
let election_payload = cacvote::Payload::Election(cacvote::Election {
jurisdiction_code: jurisdiction_code.clone(),
election_definition: election_definition.clone(),
mailing_address: "123 Main St".to_owned(),
});
let election_object = cacvote::SignedObject::from_payload(
&election_payload,
Expand Down
9 changes: 9 additions & 0 deletions apps/cacvote-jx-terminal/frontend/public/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,10 @@ video {
width: 100vw;
}

.w-20 {
width: 5rem;
}

.table-auto {
table-layout: auto;
}
Expand Down Expand Up @@ -810,6 +814,11 @@ video {
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}

.hover\:bg-purple-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(216 180 254 / var(--tw-bg-opacity));
}

.focus\:border-blue-500:focus {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
Expand Down
118 changes: 73 additions & 45 deletions apps/cacvote-jx-terminal/frontend/src/pages/elections_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::Arc;

use dioxus::prelude::*;
use dioxus_router::hooks::use_navigator;
use types_rs::cacvote::SessionData;
use types_rs::{cacvote, election};
use ui_rs::FileButton;

use crate::{
Expand All @@ -14,29 +14,44 @@ use crate::{

pub fn ElectionsPage(cx: Scope) -> Element {
let nav = use_navigator(cx);
let session_data = use_shared_state::<SessionData>(cx).unwrap();
let session_data = use_shared_state::<cacvote::SessionData>(cx).unwrap();
let session_data = &*session_data.read();
let elections = match session_data {
SessionData::Authenticated { elections, .. } => Some(elections),
_ => None,
let (elections, jurisdiction_code) = match session_data {
cacvote::SessionData::Authenticated {
elections,
jurisdiction_code,
..
} => (Some(elections), Some(jurisdiction_code)),
_ => (None, None),
};

let is_uploading = use_state(cx, || false);
let mailing_address = use_state(cx, || "".to_owned());

let upload_election = {
to_owned![is_uploading];
|election_data: Vec<u8>| async move {
to_owned![is_uploading, mailing_address];
|election_data: Vec<u8>, jurisdiction_code: cacvote::JurisdictionCode| async move {
is_uploading.set(true);

log::info!(
"election data: {}",
String::from_utf8(election_data.clone()).unwrap()
);
log::info!("election data: {}", String::from_utf8_lossy(&election_data));

let Ok(election_definition) =
election::ElectionDefinition::try_from(election_data.as_slice())
else {
return None;
};

let url = get_url("/api/elections");
let client = reqwest::Client::new();
let res = client.post(url).body(election_data).send().await;
let election = cacvote::Election {
election_definition,
jurisdiction_code,
mailing_address: mailing_address.get().clone(),
};
let res = client.post(url).json(&election).send().await;

is_uploading.set(false);
mailing_address.set("".to_owned());

Some(res)
}
Expand All @@ -45,7 +60,7 @@ pub fn ElectionsPage(cx: Scope) -> Element {
use_effect(cx, (session_data,), |(session_data,)| {
to_owned![nav, session_data];
async move {
if matches!(session_data, SessionData::Unauthenticated { .. }) {
if matches!(session_data, cacvote::SessionData::Unauthenticated { .. }) {
nav.push(Route::MachineLockedPage);
}
}
Expand Down Expand Up @@ -76,47 +91,60 @@ pub fn ElectionsPage(cx: Scope) -> Element {
)
}
},
FileButton {
class: "mt-4",
onfile: move |file_engine: Arc<dyn FileEngine>| {
cx.spawn({
to_owned![upload_election, file_engine];
async move {
if let Some(election_data) = read_file_as_bytes(file_engine).await {
match upload_election(election_data).await {
Some(Ok(response)) => {
if !response.status().is_success() {
h2 { class: "text-xl font-bold mt-8", "New Election" }
textarea {
class: "mt-4 w-30 p-2 border block",
rows: 3,
value: mailing_address.get().as_str(),
oninput: move |e| {
mailing_address.set(e.inner().value.clone());
},
placeholder: "Mailing Address",
},
if let Some(jurisdiction_code) = jurisdiction_code.cloned() {
rsx!(FileButton {
class: "mt-4",
disabled: mailing_address.get().chars().all(char::is_whitespace),
onfile: move |file_engine: Arc<dyn FileEngine>| {
cx.spawn({
to_owned![upload_election, file_engine, jurisdiction_code];
async move {
if let Some(election_data) = read_file_as_bytes(file_engine).await {
match upload_election(election_data, jurisdiction_code).await {
Some(Ok(response)) => {
if !response.status().is_success() {
web_sys::window()
.unwrap()
.alert_with_message(
&format!(
"Failed to upload election: {:?}",
response.text().await.unwrap_or("unknown error".to_owned()),
),
)
.unwrap();
return;
}
log::info!("Election uploaded successfully");
}
Some(Err(err)) => {
log::error!("Failed to upload election: {err}");
web_sys::window()
.unwrap()
.alert_with_message(
&format!(
"Failed to upload election: {:?}",
response.text().await.unwrap_or("unknown error".to_owned()),
),
&format!("Failed to upload election: {err}"),
)
.unwrap();
return;
}
log::info!("Election uploaded successfully");
}
Some(Err(err)) => {
log::error!("Failed to upload election: {err}");
web_sys::window()
.unwrap()
.alert_with_message(
&format!("Failed to upload election: {err}"),
)
.unwrap();
}
None => {
log::error!("Invalid election data");
None => {
log::error!("Invalid election data");
}
}
}
}
}
});
},
"Import Election"
});
},
"Import Election"
})
}
}
)
Expand Down
4 changes: 3 additions & 1 deletion apps/cacvote-mark/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,9 @@ function buildApi({
ballotMarkingMode: 'machine',
});

const pdf = await mailingLabel.buildPdf();
const pdf = await mailingLabel.buildPdf({
mailingAddress: election.getMailingAddress(),
});

execFileSync(
'lpr',
Expand Down
18 changes: 15 additions & 3 deletions apps/cacvote-mark/backend/src/cacvote-server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export const JournalEntrySchema: z.ZodSchema<JournalEntry> =
export class Election {
constructor(
private readonly jurisdictionCode: JurisdictionCode,
private readonly electionDefinition: ElectionDefinition
private readonly electionDefinition: ElectionDefinition,
private readonly mailingAddress: string
) {}

getJurisdictionCode(): JurisdictionCode {
Expand All @@ -140,10 +141,15 @@ export class Election {
return this.electionDefinition;
}

getMailingAddress(): string {
return this.mailingAddress;
}

toJSON(): unknown {
return {
jurisdictionCode: this.jurisdictionCode,
electionDefinition: this.electionDefinition,
mailingAddress: this.mailingAddress,
};
}
}
Expand All @@ -154,11 +160,13 @@ const ElectionStructSchema = z.object({
.string()
.transform((s) => Buffer.from(s, 'base64').toString('utf-8'))
.transform((s) => safeParseElectionDefinition(s).unsafeUnwrap()),
mailingAddress: z.string(),
});

export const ElectionSchema: z.ZodSchema<Election> =
ElectionStructSchema.transform(
(o) => new Election(o.jurisdictionCode, o.electionDefinition)
(o) =>
new Election(o.jurisdictionCode, o.electionDefinition, o.mailingAddress)
) as unknown as z.ZodSchema<Election>;

export class RegistrationRequest {
Expand Down Expand Up @@ -405,7 +413,11 @@ export const PayloadSchema: z.ZodSchema<Payload> = z
switch (o.objectType) {
case ElectionObjectType: {
return Payload.Election(
new Election(o.jurisdictionCode, o.electionDefinition)
new Election(
o.jurisdictionCode,
o.electionDefinition,
o.mailingAddress
)
);
}

Expand Down
Loading