ffmpuzzle 0.1.1

The daily FFmpeg encoding puzzle - CLI edition
use crate::data::types::{GuessResponse, LeaderboardResponse, PuzzleResponse};
use std::collections::HashMap;

const DEFAULT_BASE_URL: &str = "https://ffmpuzzle.com";

pub struct ApiClient {
    base_url: String,
    client: reqwest::Client,
}

impl ApiClient {
    pub fn new(base_url: Option<&str>) -> Self {
        Self {
            base_url: base_url.unwrap_or(DEFAULT_BASE_URL).to_string(),
            client: reqwest::Client::new(),
        }
    }

    pub async fn fetch_puzzle(&self) -> Option<PuzzleResponse> {
        let url = format!("{}/api/puzzle", self.base_url);
        self.client
            .get(&url)
            .send()
            .await
            .ok()?
            .json()
            .await
            .ok()
    }

    pub async fn submit_guess(
        &self,
        day_num: u32,
        guess_num: u32,
        values: HashMap<String, String>,
        ref_code: &str,
        session_token: Option<&str>,
    ) -> Option<GuessResponse> {
        let url = format!("{}/api/guess", self.base_url);
        let mut body = serde_json::json!({
            "dayNum": day_num,
            "guessNum": guess_num,
            "values": values,
            "refCode": ref_code,
        });
        if let Some(token) = session_token {
            body["sessionToken"] = serde_json::Value::String(token.to_string());
        }
        self.client
            .post(&url)
            .json(&body)
            .send()
            .await
            .ok()?
            .json()
            .await
            .ok()
    }

    pub async fn submit_score(
        &self,
        day_num: u32,
        ref_code: &str,
        guesses: u32,
        time_ms: u64,
        difficulty: &str,
        session_token: &str,
    ) -> bool {
        let url = format!("{}/api/score", self.base_url);
        let body = serde_json::json!({
            "dayNum": day_num,
            "refCode": ref_code,
            "guesses": guesses,
            "timeMs": time_ms,
            "difficulty": difficulty,
            "sessionToken": session_token,
        });
        self.client
            .post(&url)
            .json(&body)
            .send()
            .await
            .is_ok()
    }

    pub async fn fetch_leaderboard(&self, day_num: u32) -> Option<LeaderboardResponse> {
        let url = format!("{}/api/leaderboard/{}", self.base_url, day_num);
        self.client
            .get(&url)
            .send()
            .await
            .ok()?
            .json()
            .await
            .ok()
    }
}