depot/rust/passgen/src/main.rs

194 lines
5.4 KiB
Rust

use std::path::PathBuf;
use anyhow::{anyhow, Result};
use chrono::Local;
use clap::{Args, Parser, Subcommand};
use google_walletobjects1::api::{FlightClass, FlightObject};
use serde::Deserialize;
use tokio::try_join;
use passgen::add_to_wallet::{AddToWallet, PassClassIdentifier, PassIdentifier};
use passgen::insert_or_update::*;
use passgen::resolution792::BoardingPass;
use passgen::scanner::scan;
use passgen::service_account::load_service_account;
use passgen::walletobjects::*;
#[derive(Parser)]
#[command(author, version, about, long_about = None, propagate_version = true, subcommand_required = true, arg_required_else_help = true)]
struct Cli {
#[arg(short, long)]
service_account_file: Option<PathBuf>,
#[arg(short, long, default_value = "3388000000022186420")]
issuer_id: String,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// gets an existing pass
GetPass(GetPass),
/// gets an existing class
GetClass(GetClass),
/// uploads a class/pass pair, creating it if necessary. A Google Wallet Add link is printed.
Upload(Upload),
/// generates a class/pass pair from an image, uploads it, and then prints a Google Wallet Add
/// link.
UploadScan(UploadScan),
/// reads the aztec code out of an image
Scan {
/// Path to the image to read.
image: PathBuf,
},
}
#[derive(Args)]
struct GetClass {
#[arg(short, long)]
object_type: String,
#[arg(short, long)]
class_id: String,
}
#[derive(Args)]
struct GetPass {
#[arg(short, long)]
object_type: String,
#[arg(short, long)]
pass_id: String,
}
#[derive(Args)]
struct Upload {
#[arg(short, long)]
class_json: PathBuf,
#[arg(short, long)]
pass_json: PathBuf,
}
#[derive(Args)]
struct UploadScan {
image: PathBuf,
}
async fn load_thing<'a, T: Deserialize<'a>>(path: &PathBuf) -> Result<T> {
let data = tokio::fs::read(path).await?;
let mut deser = serde_json::Deserializer::from_reader(data.as_slice());
let parsed: T = T::deserialize(&mut deser)?;
Ok(parsed)
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let command = cli.command.unwrap();
if let Commands::Scan { image } = &command {
println!("{}", scan(image)?);
return Ok(());
};
let service_account_file = cli
.service_account_file
.ok_or_else(|| anyhow!("--service-account-file must be set"))?;
let sa = load_service_account(&service_account_file).await?;
let hub = google_walletobjects1::Walletobjects::new(
hyper::Client::builder().build(
hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.enable_http2()
.build(),
),
sa.authenticator.clone(),
);
match &command {
Commands::Scan { image: _ } => unreachable!(),
Commands::GetClass(get) => {
let class_id: String = if get.class_id.contains('.') {
get.class_id.clone()
} else {
format!("{}.{}", cli.issuer_id, get.class_id)
};
let (_, class) = hub.flightclass().get(&class_id).doit().await?;
println!("{}", serde_json::to_string(&class)?);
Ok(())
}
Commands::GetPass(get) => {
let pass_id: String = if get.pass_id.contains('.') {
get.pass_id.clone()
} else {
format!("{}.{}", cli.issuer_id, get.pass_id)
};
let (_, pass) = hub.flightobject().get(&pass_id).doit().await?;
println!("{}", serde_json::to_string(&pass)?);
Ok(())
}
Commands::Upload(upload) => {
let (pass, mut class): (FlightObject, FlightClass) = try_join!(
load_thing(&upload.pass_json),
load_thing(&upload.class_json)
)?;
// Always reset the class state to "UNDER_REVIEW".
class.review_status = Some("UNDER_REVIEW".to_string());
// Try to create the class first...
insert_or_update_now(&hub.flightclass(), &class).await?;
// Then the pass...
insert_or_update_now(&hub.flightobject(), &pass).await?;
// Now generate the URL.
println!(
"{}",
AddToWallet {
service_account_email: sa.service_account_name.to_string(),
flight_classes: vec![PassClassIdentifier {
id: class.id.clone().unwrap(),
}],
flight_passes: vec![PassIdentifier {
id: pass.id.unwrap(),
class_id: class.id.unwrap(),
}],
}
.to_url(&sa)?
);
Ok(())
}
Commands::UploadScan(upload_scan) => {
let scanned = scan(&upload_scan.image)?;
let boarding_pass =
BoardingPass::parse(scanned.as_bytes().to_vec(), &Local::now().date_naive())?;
let (classes, passes) = passes_from_barcode(&cli.issuer_id, boarding_pass)?;
// Create all the classes.
insert_or_update_many(&hub.flightclass(), classes).await?;
// Create all the objects.
insert_or_update_many(&hub.flightobject(), passes).await?;
Ok(())
}
}
}