Skip to content
Open
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
4 changes: 4 additions & 0 deletions src/backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ edition = "2021"

[dependencies]
axum = { version = "0.8.1", features = []}
dotenv = "0.15.0"
reqwest = {version="0.12.14", features = ["json"]}
serde = { version = "1.0.219", features = ["derive"]}
serde_json = "1.0.140"
tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
51 changes: 51 additions & 0 deletions src/backend/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use axum::{
routing::{get, post},
Router,
};
mod routes;

use dotenv::dotenv;
use reqwest::Client;
use routes::{
handle_create_name::create_name,
handle_formatted_name::formatted_name,
handle_name::say_name,
weather::{get_weather, AppState},
};
use std::env;
use std::sync::Arc;

const BASE_URL: &str = "0.0.0.0:5000"; // base url for server

#[tokio::main]
async fn main() {
dotenv().ok();

let base_url = env::var("BASE_URL").unwrap_or_else(|_| BASE_URL.to_string());
let weather_api_key = env::var("WEATHER_API_KEY").expect("WEATHER_API_KEY must be set");

println!("Starting server on {}", base_url);
println!("Weather API key: {}", weather_api_key);

let state = Arc::new(AppState {
client: Client::new(),
api_key: weather_api_key,
});

let app = server(state);

// run our server with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind(base_url).await.unwrap();
axum::serve(listener, app).await.unwrap();
}

// Router
pub fn server(state: Arc<AppState>) -> Router {
Router::new()
.route("/", get(|| async { "Hello, World!" }))
.route("/say-name", get(|| say_name()))
.route("/json-name", get(|| formatted_name()))
.route("/create-name", post(create_name))
.route("/weather", get(get_weather))
.with_state(state)
}
37 changes: 31 additions & 6 deletions src/backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,48 @@ use axum::{
};
mod routes;

use dotenv::dotenv;
use reqwest::Client;
use routes::{
handle_create_name::create_name, handle_formatted_name::formatted_name, handle_name::say_name,
handle_create_name::create_name,
handle_formatted_name::formatted_name,
handle_name::say_name,
weather::{get_weather, AppState},
};
use std::env;
use std::sync::Arc;

const BASE_URL: &str = "0.0.0.0:5000"; // base url for server

#[tokio::main]
async fn main() {
dotenv().ok();

let base_url = env::var("BASE_URL").unwrap_or_else(|_| BASE_URL.to_string());
let weather_api_key = env::var("WEATHER_API_KEY").expect("WEATHER_API_KEY must be set");

println!("Starting server on {}", base_url);
println!("Weather API key: {}", weather_api_key);

let state = Arc::new(AppState {
client: Client::new(),
api_key: weather_api_key,
});

let app = server(state);

// run our server with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind(BASE_URL).await.unwrap();
axum::serve(listener, server()).await.unwrap();
let listener = tokio::net::TcpListener::bind(base_url).await.unwrap();
axum::serve(listener, app).await.unwrap();
}

// Router
pub fn server() -> Router {
return Router::new()
pub fn server(state: Arc<AppState>) -> Router {
Router::new()
.route("/", get(|| async { "Hello, World!" }))
.route("/say-name", get(|| say_name()))
.route("/json-name", get(|| formatted_name()))
.route("/create-name", post(create_name));
.route("/create-name", post(create_name))
.route("/weather", get(get_weather))
.with_state(state)
}
1 change: 1 addition & 0 deletions src/backend/src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod handle_create_name;
pub mod handle_formatted_name;
pub mod handle_name;
pub mod weather;
85 changes: 85 additions & 0 deletions src/backend/src/routes/weather.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use axum::{
extract::State,
response::{IntoResponse, Json},
};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::join;

#[derive(Debug, Serialize, Deserialize)]
pub struct WeatherResponse {
pub city: String,
pub temperature: Option<f32>,
pub conditions: Option<String>,
pub error: Option<String>,
}

pub struct AppState {
pub client: Client,
pub api_key: String,
}

pub async fn get_weather(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let cities = vec!["London", "Tokyo", "New York"];

// Create futures for each city weather request
let london_future = fetch_city_weather(&state, cities[0]);
let tokyo_future = fetch_city_weather(&state, cities[1]);
let newyork_future = fetch_city_weather(&state, cities[2]);

//Execute all futures concurrently
let (london, tokyo, newyor) = join!(london_future, tokyo_future, newyork_future);

// Combine the results into a single response
let results = vec![london, tokyo, newyor];
Json(results)
}

async fn fetch_city_weather(state: &Arc<AppState>, city: &str) -> WeatherResponse {
let url = format!(
"https://api.weatherapi.com/v1/current.json?key={}&q={}&aqi=no",
state.api_key, city
);

match state.client.get(&url).send().await {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>().await {
Ok(json) => {
let temp = json["current"]["temp_c"].as_f64().map(|t| t as f32);
let cond = json["current"]["condition"]["text"]
.as_str()
.map(String::from);

WeatherResponse {
city: city.to_string(),
temperature: temp,
conditions: cond,
error: None,
}
}
Err(e) => WeatherResponse {
city: city.to_string(),
temperature: None,
conditions: None,
error: Some(format!("Failed to parse JSON: {}", e)),
},
}
} else {
WeatherResponse {
city: city.to_string(),
temperature: None,
conditions: None,
error: Some(format!("API error: {}", response.status())),
}
}
}
Err(e) => WeatherResponse {
city: city.to_string(),
temperature: None,
conditions: None,
error: Some(format!("Request failed: {}", e)),
},
}
}