bounds-cli 0.5.1

Add a .gitignore and LICENSE to a repository
use std::{env, time::Duration};

use anyhow::{Context, Result};
use serde::de::DeserializeOwned;
use ureq::Agent;

use crate::core::model::{LicenseSummary, LicenseTemplate};

const API_URL: &str = "https://api.github.com";
const API_VERSION: &str = "2026-03-10";

#[derive(Clone)]
pub struct GitHubClient {
    agent: Agent,
    token: Option<String>,
}

impl GitHubClient {
    pub fn new() -> Self {
        let config = Agent::config_builder()
            .timeout_global(Some(Duration::from_secs(15)))
            .build();

        Self {
            agent: config.into(),
            token: env::var("GITHUB_TOKEN").ok(),
        }
    }

    pub fn licenses(&self) -> Result<Vec<LicenseSummary>> {
        self.get_json(&format!("{API_URL}/licenses?per_page=100"))
    }

    pub fn license(&self, license: &LicenseSummary) -> Result<LicenseTemplate> {
        self.get_json(&license.url)
    }

    fn get_json<T>(&self, url: &str) -> Result<T>
    where
        T: DeserializeOwned,
    {
        let mut request = self
            .agent
            .get(url)
            .header("Accept", "application/vnd.github+json")
            .header("X-GitHub-Api-Version", API_VERSION)
            .header("User-Agent", concat!("bounds/", env!("CARGO_PKG_VERSION")));

        let authorization = self.token.as_ref().map(|token| format!("Bearer {token}"));

        if let Some(authorization) = authorization.as_deref() {
            request = request.header("Authorization", authorization);
        }

        let mut response = request
            .call()
            .with_context(|| format!("GitHub request failed: {url}"))?;

        response
            .body_mut()
            .read_json::<T>()
            .with_context(|| format!("GitHub returned invalid data: {url}"))
    }
}