Skip to main content

norman_ai/
lib.rs

1use serde::{Deserialize, Serialize};
2use reqwest::Client;
3use anyhow::{anyhow, Result};
4
5
6/// Base API URL
7const BASE_URL: &str = "https://api.dev.amit.public.norman-ai.com/v0";
8
9/// Account model
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Account {
12    pub id: String,
13    pub creation_time: String,
14    pub name: String,
15}
16
17/// Authentication factors
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AccountAuthenticationFactors {
20    pub account_id: String,
21    pub api_key_count: u32,
22    pub password_count: u32,
23    pub verified_email_count: u32,
24}
25
26impl AccountAuthenticationFactors {
27    pub fn has_authentication_factor(&self) -> bool {
28        self.api_key_count > 0 || self.password_count > 0 || self.verified_email_count > 0
29    }
30}
31
32/// Login response
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct LoginResponse {
35    pub account: Account,
36    pub access_token: String,
37    pub id_token: String,
38}
39
40/// Norman API service
41pub struct AccountService {
42    client: Client,
43}
44
45impl AccountService {
46    pub fn new() -> Self {
47        Self {
48            client: Client::new(),
49        }
50    }
51
52    /// Fetch a single account by ID
53    pub async fn get_account_by_id(&self, account_id: &str) -> Result<Account> {
54        let payload = serde_json::json!({
55            "table": "Accounts",
56            "filters": { "id": account_id }
57        });
58
59        let resp = self.client
60            .post(format!("{BASE_URL}/authenticate/accounts/get"))
61            .json(&payload)
62            .send()
63            .await?;
64
65        let map: serde_json::Value = resp.json().await?;
66        if let Some(obj) = map.as_object().and_then(|o| o.values().next()) {
67            Ok(serde_json::from_value(obj.clone())?)
68        } else {
69            Err(anyhow!("No account found for id: {account_id}"))
70        }
71    }
72
73    /// Fetch multiple accounts with optional filters
74    pub async fn get_accounts(
75        &self,
76        filters: Option<serde_json::Value>,
77    ) -> Result<Vec<Account>, reqwest::Error> {
78        let payload = if let Some(f) = filters {
79            serde_json::json!({ "filters": f })
80        } else {
81            serde_json::json!({})
82        };
83
84        let resp = self.client
85            .post(format!("{BASE_URL}/authenticate/accounts/get"))
86            .json(&payload)
87            .send()
88            .await?;
89
90        let map: serde_json::Value = resp.json().await?;
91        let mut accounts = vec![];
92        if let Some(obj) = map.as_object() {
93            for v in obj.values() {
94                if let Ok(a) = serde_json::from_value::<Account>(v.clone()) {
95                    accounts.push(a);
96                }
97            }
98        }
99        Ok(accounts)
100    }
101
102    /// Retrieve authentication factors for an account
103    pub async fn get_authentication_factors(
104        &self,
105        account_id: &str,
106    ) -> Result<AccountAuthenticationFactors, reqwest::Error> {
107        let url = format!("{BASE_URL}/authenticate/register/get/authentication/factors/{account_id}");
108        let resp = self.client.get(url).send().await?;
109        let data = resp.json::<AccountAuthenticationFactors>().await?;
110        Ok(data)
111    }
112
113    /// Update account name
114    pub async fn update_account_name(
115        &self,
116        account_id: &str,
117        name: &str,
118    ) -> Result<(), reqwest::Error> {
119        let payload = serde_json::json!({
120            "account": { "name": name },
121            "filters": { "id": account_id }
122        });
123
124        self.client
125            .patch(format!("{BASE_URL}/authenticate/accounts"))
126            .json(&payload)
127            .send()
128            .await?
129            .error_for_status()?;
130
131        Ok(())
132    }
133}