threexui-rs 2.9.5

Async Rust SDK for the 3x-ui panel API (compatible with 3x-ui v2.9.2 and v2.9.3)
Documentation
use crate::models::custom_geo::{CreateCustomGeo, CustomGeoAliases, CustomGeoResource};
use crate::{Client, Result};

pub struct CustomGeoApi<'a> {
    pub(crate) client: &'a Client,
}

impl<'a> CustomGeoApi<'a> {
    pub async fn list(&self) -> Result<Vec<CustomGeoResource>> {
        self.client.get("panel/api/custom-geo/list").await
    }

    pub async fn aliases(&self) -> Result<CustomGeoAliases> {
        self.client.get("panel/api/custom-geo/aliases").await
    }

    pub async fn add(&self, geo: &CreateCustomGeo) -> Result<()> {
        self.client
            .post_empty("panel/api/custom-geo/add", geo)
            .await
    }

    pub async fn update(&self, id: i64, geo: &CreateCustomGeo) -> Result<()> {
        self.client
            .post_empty(&format!("panel/api/custom-geo/update/{}", id), geo)
            .await
    }

    pub async fn delete(&self, id: i64) -> Result<()> {
        self.client
            .post_empty(
                &format!("panel/api/custom-geo/delete/{}", id),
                &serde_json::json!({}),
            )
            .await
    }

    pub async fn download(&self, id: i64) -> Result<()> {
        self.client
            .post_empty(
                &format!("panel/api/custom-geo/download/{}", id),
                &serde_json::json!({}),
            )
            .await
    }

    pub async fn update_all(&self) -> Result<serde_json::Value> {
        self.client
            .post("panel/api/custom-geo/update-all", &serde_json::json!({}))
            .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::ClientConfig;
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    async fn auth_client(server: &MockServer) -> Client {
        Mock::given(method("POST"))
            .and(path("/login"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
                "success": true, "msg": "", "obj": null
            })))
            .mount(server)
            .await;
        let config = ClientConfig::builder()
            .host("127.0.0.1")
            .port(server.address().port())
            .build()
            .unwrap();
        let client = Client::new(config);
        client.login("admin", "pass").await.unwrap();
        client
    }

    #[tokio::test]
    async fn list_returns_custom_geos() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/panel/api/custom-geo/list"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
                "success": true, "msg": "", "obj": [
                    {"id":1,"type":"geoip","alias":"myip","url":"https://example.com/ip.dat",
                     "localPath":"","lastUpdatedAt":0,"createdAt":0,"updatedAt":0}
                ]
            })))
            .mount(&server)
            .await;

        let client = auth_client(&server).await;
        let list = client.custom_geo().list().await.unwrap();
        assert_eq!(list.len(), 1);
        assert_eq!(list[0].alias, "myip");
    }

    #[tokio::test]
    async fn add_custom_geo_succeeds() {
        let server = MockServer::start().await;
        Mock::given(method("POST"))
            .and(path("/panel/api/custom-geo/add"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
                "success": true, "msg": "added", "obj": null
            })))
            .mount(&server)
            .await;

        let client = auth_client(&server).await;
        let geo = CreateCustomGeo {
            geo_type: "geoip".into(),
            alias: "myip".into(),
            url: "https://example.com/ip.dat".into(),
        };
        client.custom_geo().add(&geo).await.unwrap();
    }
}