Преглед на файлове

is_logged_in and log_in endpoints added

Yurii Sokolovskyi преди 1 месец
родител
ревизия
eae99a35df
променени са 3 файла, в които са добавени 144 реда и са изтрити 18 реда
  1. 66 4
      src/config.rs
  2. 17 13
      src/main.rs
  3. 61 1
      src/server.rs

+ 66 - 4
src/config.rs

@@ -10,10 +10,11 @@
 // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 // OF THIS SOFTWARE.
 
-use std::sync::OnceLock;
+use std::sync::{OnceLock, RwLock};
 use std::fs::File;
-use std::io::{self, BufRead};
+use std::io::{self, BufRead, BufReader, BufWriter, Write};
 use std::path::{Path, PathBuf};
+use crate::arg;
 
 // Ini-like configuration, with sections.
 // Global config first, then config for each subsection
@@ -89,10 +90,10 @@ impl Subsection {
     }
 }
 
-pub static INSTANCE: OnceLock<Config> = OnceLock::new();
+pub static INSTANCE: OnceLock<RwLock<Config>> = OnceLock::new();
 
 impl Config {
-    pub fn global() -> &'static Config {
+    pub fn global() -> &'static RwLock<Config> {
         INSTANCE.get().expect("Config is not initialized")
     }
 
@@ -148,4 +149,65 @@ impl Config {
         }
         Ok(conf)
     }
+    
+    pub fn set_username(&mut self, username: String){
+        self.username = username;
+    }
+
+    pub fn set_password(&mut self, password: String){
+        self.password = password;
+    }
+
+    pub fn update_credentials(
+        &mut self,
+        new_username: &str,
+        new_password: &str,
+    ) -> io::Result<()> {
+        let args = arg::Args::from_env();
+        
+        // Open the existing configuration file for reading
+        let file = File::open(&args.config)?;
+        let reader = BufReader::new(file);
+
+        // Create a temporary file for writing the updated configuration
+        let temp_path = args.config.clone().as_ref().with_extension("tmp");
+        let temp_file = File::create(&temp_path)?;
+        let mut writer = BufWriter::new(temp_file);
+
+        // Flags to check if username and password have been updated
+        let mut username_updated = false;
+        let mut password_updated = false;
+
+        // Read the configuration line by line
+        for line in reader.lines() {
+            let mut line = line?;
+            if line.starts_with("username=") {
+                // Update the username
+                line = format!("username={}", new_username);
+                username_updated = true;
+            } else if line.starts_with("password=") {
+                // Update the password
+                line = format!("password={}", new_password);
+                password_updated = true;
+            }
+            // Write the line to the temporary file
+            writeln!(writer, "{}", line)?;
+        }
+
+        // If username or password were not found, append them
+        if !username_updated {
+            writeln!(writer, "username={}", new_username)?;
+        }
+        if !password_updated {
+            writeln!(writer, "password={}", new_password)?;
+        }
+
+        // Ensure all data is flushed to the temporary file
+        writer.flush()?;
+
+        // Replace the original configuration file with the updated temporary file
+        std::fs::rename(temp_path, &args.config)?;
+
+        Ok(())
+    }
 }

+ 17 - 13
src/main.rs

@@ -10,7 +10,7 @@ use std::collections::HashMap;
 use std::fs::{create_dir_all, File};
 use std::future::Future;
 use std::io::{Write};
-use std::sync::Mutex;
+use std::sync::{Mutex, RwLock};
 use std::time::{Instant};
 use anyhow::anyhow;
 use serde::{Deserialize, Serialize};
@@ -379,24 +379,16 @@ fn parse_args(){
     
     config.maildir = args.maildir.clone();
     config.out_dir = args.out_dir.clone();
-    INSTANCE.set(config).unwrap();
+    INSTANCE.set(RwLock::new(config)).unwrap();
 }
 
-async fn run(){
-    // parse args from CLI
-    parse_args();
-
-    // API
-    let api_task = tokio::spawn(async move {
-        run_api().await
-    });
-    
+pub async fn run_download(){
     // downloading new emails
     match imap::download_email_from_imap().await {
         Ok(_) => {}
         Err(_) => println!("Cannot download new emails")
     }
-    
+
     // Uncomment to convert to HTML without downloading
     // let mut uid = 1; // fake uid for tests
     // for entry in std::fs::read_dir(Config::global().maildir.clone().join("INBOX").join("new")).expect("could not read maildir").filter_map(|m| m.ok()){
@@ -410,13 +402,25 @@ async fn run(){
     //     uid += 1;
     // }
 
-    
+
     // start updates monitoring
     for mailbox in Indexes::get_local_mailboxes() {
         let handler = check_for_updates(mailbox.clone());
         let mut idle_task_storage = IDLE_TASK_STORAGE.lock().unwrap();
         idle_task_storage.add(mailbox.clone(), handler);
     }
+} 
+
+async fn run(){
+    // parse args from CLI
+    parse_args();
+
+    // API
+    let api_task = tokio::spawn(async move {
+        run_api().await
+    });
+
+    run_download().await;
     
     // dont stop the program while API is running
     api_task.await.expect("Error while running API");

+ 61 - 1
src/server.rs

@@ -3,6 +3,7 @@ use crate::config::Config;
 use crate::indexes::{Indexes, SerializableMessage, SerializableThread};
 use serde::{Deserialize, Serialize};
 use std::fs::{File, metadata};
+use std::future::Future;
 use std::io::{BufReader, Read};
 use std::net::{IpAddr, SocketAddr};
 use std::path::PathBuf;
@@ -10,8 +11,9 @@ use std::str::FromStr;
 use chrono::{DateTime, Utc};
 use warp::Filter;
 use warp::fs::file;
-use warp::http::StatusCode;
+use warp::http::{response, StatusCode};
 use crate::{create_folder_lar, delete_folder_lar, rename_folder_lar};
+use crate::imap::connect_to_imap;
 use crate::models::MailAddress;
 use crate::sonic::SearchSonic;
 use crate::util::read_and_decompress_file;
@@ -74,6 +76,13 @@ pub async fn run_api() {
             .and(warp::post())
             .and(warp::body::json())
             .and_then(delete_folder_handle))
+        .or(warp::path("is_logged_in")
+            .and(warp::get())
+            .and_then(is_logged_in_handle))
+        .or(warp::path("log_in")
+            .and(warp::post())
+            .and(warp::body::json())
+            .and_then(log_in_handle))
         .with(cors);
 
     let ip: IpAddr = IpAddr::from_str(&*Config::global().api_addr.clone()).expect("Invalid API IP address");
@@ -271,6 +280,34 @@ pub async fn get_sorted_threads_by_date(folder: String, limit: usize, offset: us
     get_threads(folder, limit, offset, "date.json".to_string()).await
 }
 
+pub async fn is_logged_in() -> String {
+    let response = if (Config::global().username == "" || Config::global().password == "") {
+        serde_json::to_string(&IsLoggedInResponse { is_logged_in: false })
+    }else{
+        serde_json::to_string(&IsLoggedInResponse { is_logged_in: true })
+    };
+
+    response.unwrap_or_else(|_| serde_json::to_string(&IsLoggedInResponse { is_logged_in: true }).unwrap())
+}
+
+pub async fn log_in(email: String, password: String) -> bool {
+    Config::global().set_username(email.clone());
+    Config::global().set_password(password.clone());
+    
+    match connect_to_imap().await { 
+        Ok(_) => {
+            match Config::global().update_credentials(&email, &password){
+                Ok(_) => { 
+                    // TODO start downloading emails
+                    true
+                },
+                Err(_) => false
+            }
+        }
+        Err(_) => false
+    }
+}
+
 // Handlers
 #[derive(Deserialize)]
 struct GetAttachmentQuery {
@@ -443,4 +480,27 @@ async fn search_handle(query: SearchQuery) -> Result<impl warp::Reply, warp::Rej
         Ok(result) => Ok(warp::reply::json(&result)),
         Err(_) => Ok(warp::reply::json(&Vec::<String>::new()))
     }
+}
+
+#[derive(Deserialize, Serialize)]
+struct IsLoggedInResponse{
+    is_logged_in: bool
+}
+
+async fn is_logged_in_handle() -> Result<impl warp::Reply, warp::Rejection>{
+    Ok(warp::reply::json(&is_logged_in().await))
+}
+
+#[derive(Deserialize)]
+struct LogInRequest {
+    email: String,
+    password: String
+}
+
+async fn log_in_handle(query: LogInRequest) -> Result<impl warp::Reply, warp::Rejection>{
+    if log_in(query.email, query.password).await { 
+        Ok(warp::reply::with_status("Successful log in", StatusCode::OK))
+    }else{
+        Ok(warp::reply::with_status("Unable to log in", StatusCode::BAD_REQUEST))
+    }
 }