use reqwest::Client as HttpClient; use rick_and_morty::models::{Character, OriginOrLocation}; use serde::{Deserialize, Serialize}; use sqlx::{Row, SqlitePool}; #[derive(Debug, Deserialize, Serialize)] struct ApiCharacter { id: i32, name: String, status: String, species: String, #[serde(rename = "type")] character_type: String, gender: String, origin: OriginOrLocation, location: OriginOrLocation, image: String, episode: Vec, url: String, created: String, } impl From for Character { fn from(api: ApiCharacter) -> Self { Character { id: 0, // Ignored for new/incoming data; SQLite autoincrements rmid: api.id, name: api.name, status: api.status, species: api.species, r#type: api.character_type, gender: api.gender, origin_name: api.origin.name, origin_url: api.origin.url, location_name: api.location.name, location_url: api.location.url, image: api.image, // Store as JSON string episode: serde_json::to_string(&api.episode).unwrap(), url: api.url, created: api.created, elo_rating: 1000.0, } } } fn init_tracing() { use tracing_subscriber::EnvFilter; tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .with_target(true) .with_level(true) .init(); } #[tokio::main] async fn main() -> anyhow::Result<()> { init_tracing(); dotenvy::dotenv().ok(); let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL not set"); let pool = SqlitePool::connect(&db_url).await?; tracing::info!("Starting to fetch characters from Rick and Morty API"); let http = HttpClient::new(); let mut all_characters: Vec = Vec::new(); let mut next_url = "https://rickandmortyapi.com/api/character".to_string(); while !next_url.is_empty() { tracing::info!(url = %next_url, "Fetching page"); let resp = http .get(&next_url) .send() .await? .json::() .await?; let results = resp["results"].as_array().unwrap(); for c in results { let c: ApiCharacter = serde_json::from_value(c.clone()).unwrap(); all_characters.push(c.into()); } next_url = resp["info"]["next"].as_str().unwrap_or("").to_string(); } tracing::info!( count = all_characters.len(), "Fetched all characters, starting DB upsert" ); for character in &all_characters { // Try to fetch existing character by rmid let row = sqlx::query("SELECT id, elo_rating FROM characters WHERE rmid = ?") .bind(character.rmid) .fetch_optional(&pool) .await?; if let Some(row) = row { // Exists: update all fields except elo_rating let id: i64 = row.get("id"); sqlx::query( "UPDATE characters SET name = ?, status = ?, species = ?, type = ?, gender = ?, origin_name = ?, origin_url = ?, location_name = ?, location_url = ?, image = ?, episode = ?, url = ?, created = ? WHERE id = ?", ) .bind(&character.name) .bind(&character.status) .bind(&character.species) .bind(&character.r#type) .bind(&character.gender) .bind(&character.origin_name) .bind(&character.origin_url) .bind(&character.location_name) .bind(&character.location_url) .bind(&character.image) .bind(&character.episode) .bind(&character.url) .bind(&character.created) .bind(id) .execute(&pool) .await?; tracing::info!(id = character.rmid, name = %character.name, "Updated character (no elo overwrite)"); } else { // Insert new character with default elo_rating sqlx::query( "INSERT INTO characters ( rmid, name, status, species, type, gender, origin_name, origin_url, location_name, location_url, image, episode, url, created, elo_rating ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ) .bind(character.rmid) .bind(&character.name) .bind(&character.status) .bind(&character.species) .bind(&character.r#type) .bind(&character.gender) .bind(&character.origin_name) .bind(&character.origin_url) .bind(&character.location_name) .bind(&character.location_url) .bind(&character.image) .bind(&character.episode) .bind(&character.url) .bind(&character.created) .bind(1000.0) .execute(&pool) .await?; tracing::info!(id = character.rmid, name = %character.name, "Inserted new character"); } } // tracing::info!("Inserted {} characters", insert_result.inserted_ids.len()); let character_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM characters") .fetch_one(&pool) .await?; tracing::info!( count = character_count, "Total characters in DB after import" ); tracing::info!("Done! Imported/updated characters."); Ok(()) }