use axum::extract::Query;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Redirect};
use axum::Json;
use axum::{routing::get, Router};
use serde::Deserialize;
use serde_json::json;
use smartcar::*;
use auth_client::{AuthClient, AuthUrlOptionsBuilder};
use response::{Meta, VehicleAttributes};
use vehicle::Vehicle;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/login", get(login))
.route("/callback", get(callback));
println!("\nStep 0: Running on localhost, port 3000");
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
fn get_auth_client() -> AuthClient {
AuthClient::new(
"REPLACE_WITH_YOUR_SMARTCAR_CLIENT_ID",
"REPLACE_WITH_YOUR_SMARTCAR_CLIENT_SECRET",
"REPLACE_WITH_YOUR_SMARTCAR_REDIRECT_URI.COM",
true,
)
}
async fn login() -> Redirect {
let auth_client = get_auth_client();
println!(
"\nStep 1: Create an AuthClient struct with your client credentials:\n{:#?}\n",
auth_client
);
let scope = ScopeBuilder::new().add_permissions([Permission::ReadVehicleInfo]);
let auth_url_options = AuthUrlOptionsBuilder::new().set_force_prompt(true);
let auth_url = auth_client.get_auth_url(&scope, Some(&auth_url_options));
println!("Step 2: Generating the auth url that your user will go to");
println!(
"\nResult: Generated auth URL. Redirecting to:\n\n{}",
auth_url
);
Redirect::to(&auth_url)
}
#[derive(Deserialize)]
struct Callback {
code: Option<String>,
error: Option<String>,
}
async fn callback(q: Query<Callback>) -> impl IntoResponse {
println!("\nStep 3: Completed in browser, i.e. User finished connect flow");
println!("\nStep 4a: Redirecting to /callback");
if q.error.is_some() {
return (
StatusCode::EXPECTATION_FAILED,
Json(json!("User delined during Smartcar Connect")),
);
};
let code = &q.code.to_owned().unwrap();
println!("\nResult: `code` query present in /callback:\n{}", code);
match get_attributes_handler(code).await {
Err(_) => (
StatusCode::EXPECTATION_FAILED,
Json(json!("attributes request failed")),
),
Ok((attributes, _)) => {
(
StatusCode::OK,
Json(json!(attributes)), )
}
}
}
async fn get_attributes_handler(
auth_code: &str,
) -> Result<(VehicleAttributes, Meta), smartcar::error::Error> {
let client = get_auth_client();
let (access, _) = client.exchange_code(auth_code).await?;
println!(
"\nStep 4b: Exchange code for an Access struct with tokens:\n{:#?}\n",
access
);
let (vehicle_ids, _) = smartcar::get_vehicles(&access, None, None).await?;
println!(
"\nStep 5: Use access token to get the user's vehicles (i.e. a list of vehicle ids):\n{:#?}",
vehicle_ids
);
let vehicle = Vehicle::new(&vehicle_ids.vehicles[0], &access.access_token);
println!(
"\nStep 6: Use any id from the Vehicles (plural) instance to make a single Vehicle struct:\n{:#?}",
vehicle
);
println!("\nStep 7: Send a request to get the vehicle's attributes");
let (attributes, meta) = vehicle.attributes().await?;
println!(
"\nResult: Got the vehicle's id, make, model, and year:\n{:#?}",
attributes
);
println!(
"\nAlso got information about the request itself:\n{:#?}",
meta
);
println!("\nTHE END");
Ok((attributes, meta))
}