use std::env; use dotenv::dotenv; use reqwest::{Client, Error}; use serde::{Deserialize, Serialize}; use uuid::Uuid; // Structs for JSON payloads and responses #[derive(Serialize, Deserialize, Debug)] struct CreateUserRequest { id: String, email: String, #[serde(skip_serializing_if = "Option::is_none")] email_confirm: Option, } #[derive(Serialize, Deserialize, Debug)] struct InviteRequest { email: String, } #[derive(Serialize, Deserialize, Debug)] struct RecoverRequest { email: String, } #[derive(Serialize, Deserialize, Debug)] struct UpdateUserRequest { password: String, } #[derive(Serialize, Deserialize, Debug)] pub struct User { pub id: String, pub email: String, pub aud: String, pub role: String, pub created_at: String, pub updated_at: String, pub email_confirmed_at: Option, } // Main Supabase client struct pub struct SupabaseClient { url: String, service_role_key: String, client: Client, } impl SupabaseClient { // Initialize the client with env variables pub fn new() -> Result { dotenv().ok(); // Load .env file let url = env::var("SUPABASE_URL")?; let service_role_key = env::var("SUPABASE_SERVICE_ROLE_KEY")?; Ok(Self { url, service_role_key, client: Client::new(), }) } // 1. Create a new user with a specific ID pub async fn create_user(&self, id: Uuid, email: &str) -> Result { let payload = CreateUserRequest { id: id.to_string(), email: email.to_string(), email_confirm: Some(false), // Email unconfirmed initially }; let response = self .client .post(format!("{}/auth/v1/admin/users", self.url)) .header("Authorization", format!("Bearer {}", self.service_role_key)) .header("Content-Type", "application/json") .header("apikey", &self.service_role_key) .json(&payload) .send() .await?; let user = response.json::().await?; Ok(user) } // 2. Send a verification email pub async fn send_verification_email(&self, email: &str) -> Result<(), Error> { let payload = InviteRequest { email: email.to_string(), }; self.client .post(format!("{}/auth/v1/invite", self.url)) .header("Authorization", format!("Bearer {}", self.service_role_key)) .header("Content-Type", "application/json") .header("apikey", &self.service_role_key) .json(&payload) .send() .await?; Ok(()) } // 3a. Trigger a password reset email (for user to set password) pub async fn send_password_reset_email(&self, email: &str) -> Result<(), Error> { let payload = RecoverRequest { email: email.to_string(), }; self.client .post(format!("{}/auth/v1/recover", self.url)) .header("Authorization", format!("Bearer {}", self.service_role_key)) .header("Content-Type", "application/json") .header("apikey", &self.service_role_key) .json(&payload) .send() .await?; Ok(()) } // 3b. Update user password (simulating user action with recovery token) pub async fn update_user_password(&self, recovery_token: &str, password: &str) -> Result<(), Error> { let payload = UpdateUserRequest { password: password.to_string(), }; self.client .put(format!("{}/auth/v1/user", self.url)) .header("Authorization", format!("Bearer {}", recovery_token)) .header("Content-Type", "application/json") .header("apikey", &self.service_role_key) .json(&payload) .send() .await?; Ok(()) } } // Example usage in a main function (for testing) #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_supabase_client() { let client = SupabaseClient::new().expect("Failed to initialize client"); // 1. Create a user let user_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); let user = client.create_user(user_id, "newuser@example.com").await.unwrap(); println!("Created user: {:?}", user); // 2. Send verification email client.send_verification_email("newuser@example.com").await.unwrap(); println!("Verification email sent. Check Inbucket at http://localhost:54324"); // 3. Simulate password reset (manual token extraction needed from email) client.send_password_reset_email("newuser@example.com").await.unwrap(); println!("Password reset email sent. Check Inbucket for recovery token"); // Normally, you'd extract the recovery token from the email and pass it here // For testing, you'd need to manually get it from Inbucket // client.update_user_password("", "newsecurepassword123").await.unwrap(); } }