rust_kanban/io/
io_handler.rs

1use crate::{
2    app::{
3        app_helper::handle_go_to_previous_view, kanban::Boards, state::UserLoginData, App,
4        AppConfig,
5    },
6    constants::{
7        CONFIG_DIR_NAME, CONFIG_FILE_NAME, EMAIL_REGEX, ENCRYPTION_KEY_FILE_NAME,
8        MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH, MIN_TIME_BETWEEN_SENDING_RESET_LINK,
9        REFRESH_TOKEN_FILE_NAME, REFRESH_TOKEN_SEPARATOR, SAVE_DIR_NAME, SUPABASE_ANON_KEY,
10        SUPABASE_URL,
11    },
12    io::{
13        data_handler::{
14            get_available_local_save_files, get_default_save_directory, get_local_kanban_state,
15            get_saved_themes, save_kanban_state_locally,
16        },
17        IoEvent,
18    },
19    ui::{TextColorOptions, View},
20    util::{print_debug, print_error, print_info},
21};
22use aes_gcm::{
23    aead::{generic_array::GenericArray, Aead, OsRng},
24    AeadCore, Aes256Gcm, Key, KeyInit,
25};
26use base64::Engine;
27use chrono::{NaiveDate, NaiveDateTime};
28use eyre::{anyhow, Result};
29use linked_hash_map::LinkedHashMap;
30use log::{debug, error, info, warn};
31use ratatui::widgets::ListState;
32use reqwest::StatusCode;
33use serde::{Deserialize, Serialize};
34use serde_json::json;
35use std::{
36    env,
37    path::{Path, PathBuf},
38    sync::Arc,
39    time::{Duration, Instant},
40};
41
42pub struct IoAsyncHandler<'a> {
43    app: Arc<tokio::sync::Mutex<App<'a>>>,
44}
45
46impl IoAsyncHandler<'_> {
47    pub fn new(app: Arc<tokio::sync::Mutex<App>>) -> IoAsyncHandler {
48        IoAsyncHandler { app }
49    }
50
51    pub async fn handle_io_event(&mut self, io_event: IoEvent) {
52        let result = match io_event {
53            IoEvent::Initialize => self.do_initialize().await,
54            IoEvent::SaveLocalData => self.save_local_data().await,
55            IoEvent::LoadSaveLocal => self.load_save_file_local().await,
56            IoEvent::DeleteLocalSave => self.delete_local_save_file().await,
57            IoEvent::ResetVisibleBoardsandCards => self.refresh_visible_boards_and_cards().await,
58            IoEvent::AutoSave => self.auto_save().await,
59            IoEvent::LoadLocalPreview => self.load_local_preview().await,
60            IoEvent::Login(email_id, password) => self.cloud_login(email_id, password).await,
61            IoEvent::Logout => self.cloud_logout().await,
62            IoEvent::SignUp(email_id, password, confirm_password) => {
63                self.cloud_signup(email_id, password, confirm_password)
64                    .await
65            }
66            IoEvent::SendResetPasswordEmail(email_id) => {
67                self.send_reset_password_email(email_id).await
68            }
69            IoEvent::ResetPassword(reset_link, new_password, confirm_password) => {
70                self.reset_password(reset_link, new_password, confirm_password)
71                    .await
72            }
73            IoEvent::SyncLocalData => self.sync_local_data().await,
74            IoEvent::GetCloudData => self.get_cloud_data().await,
75            IoEvent::LoadSaveCloud => self.load_save_file_cloud().await,
76            IoEvent::LoadCloudPreview => self.preview_cloud_save().await,
77            IoEvent::DeleteCloudSave => self.delete_cloud_save().await,
78        };
79
80        let mut app = self.app.lock().await;
81        if let Err(err) = result {
82            error!("Oops, something wrong happened 😢: {:?}", err);
83            app.send_error_toast("Oops, something wrong happened 😢", None);
84        }
85
86        app.loaded();
87    }
88
89    async fn do_initialize(&mut self) -> Result<()> {
90        info!("🚀 Initialize the application");
91        let mut app = self.app.lock().await;
92        let default_ui_view = app.config.default_view;
93        let prepare_config_dir_status = prepare_config_dir();
94        if prepare_config_dir_status.is_err() {
95            error!("Cannot create config directory");
96            app.send_error_toast("Cannot create config directory", None);
97        }
98        if !prepare_save_dir() {
99            error!("Cannot create save directory");
100            app.send_error_toast("Cannot create save directory", None);
101        }
102        prepare_boards(&mut app);
103        app.dispatch(IoEvent::ResetVisibleBoardsandCards).await;
104        let saved_themes = get_saved_themes();
105        if let Some(saved_themes) = saved_themes {
106            app.all_themes.extend(saved_themes);
107        }
108        let default_theme = app.config.default_theme.clone();
109        for theme in &app.all_themes {
110            if theme.name == default_theme {
111                app.current_theme = theme.clone();
112                break;
113            }
114        }
115        if let Some(bg_color) = app.current_theme.general_style.bg {
116            app.state.term_background_color = TextColorOptions::from(bg_color).to_rgb();
117        } else {
118            app.state.term_background_color = (0, 0, 0)
119        }
120        app.set_view(default_ui_view);
121        info!("👍 Application initialized");
122        app.initialized();
123        if app.config.save_directory == get_default_save_directory() {
124            app.send_warning_toast(
125                "Save directory is set to a temporary directory,
126            your operating system may delete it at any time. Please change it in the settings.",
127                Some(Duration::from_secs(10)),
128            );
129        }
130        app.send_info_toast("Application initialized", None);
131        if app.config.auto_login {
132            app.send_info_toast("Attempting to auto login", None);
133            let user_login_data =
134                test_refresh_token_on_disk(app.state.encryption_key_from_arguments.clone()).await;
135            if user_login_data.is_err() {
136                let refresh_token_file_path = get_config_dir();
137                if refresh_token_file_path.is_err() {
138                    error!("Cannot get config directory");
139                    app.send_error_toast("Cannot get config directory", None);
140                    return Ok(());
141                }
142                let mut refresh_token_file_path = refresh_token_file_path.unwrap();
143                refresh_token_file_path.push(REFRESH_TOKEN_FILE_NAME);
144                if refresh_token_file_path.exists() {
145                    if let Err(err) = std::fs::remove_file(refresh_token_file_path) {
146                        error!("Cannot delete refresh token file: {:?}", err);
147                        app.send_error_toast("Cannot delete refresh token file", None);
148                        return Ok(());
149                    } else {
150                        warn!("Previous access token has expired or does not exist. Please login again");
151                        app.send_warning_toast("Previous access token has expired or does not exist. Please login again", None)
152                    }
153                } else {
154                    warn!(
155                        "Previous access token has expired or does not exist. Please login again"
156                    );
157                    app.send_warning_toast(
158                        "Previous access token has expired or does not exist. Please login again",
159                        None,
160                    )
161                }
162            } else {
163                let user_login_data = user_login_data.unwrap();
164                app.state.user_login_data = user_login_data;
165                app.main_menu.logged_in = true;
166                app.send_info_toast("👍 Auto login successful", None);
167            }
168        }
169        Ok(())
170    }
171
172    async fn save_local_data(&mut self) -> Result<()> {
173        info!("🚀 Saving local data");
174        let mut app = self.app.lock().await;
175        if save_required(&mut app) {
176            let board_data = app.boards.get_boards();
177            let status = save_kanban_state_locally(board_data.to_vec(), &app.config);
178            match status {
179                Ok(_) => {
180                    info!("👍 Local data saved");
181                    app.send_info_toast("👍 Local data saved", None);
182                }
183                Err(err) => {
184                    debug!("Cannot save local data: {:?}", err);
185                    app.send_error_toast("Cannot save local data", None);
186                }
187            }
188            Ok(())
189        } else {
190            warn!("No changes to save");
191            app.send_warning_toast("No changes to save", None);
192            Ok(())
193        }
194    }
195
196    async fn load_save_file_local(&mut self) -> Result<()> {
197        let mut app = self.app.lock().await;
198        let default_view = app.config.default_view;
199        let save_file_index = app.state.app_list_states.load_save.selected().unwrap_or(0);
200        let local_files = get_available_local_save_files(&app.config);
201        let local_files = if let Some(local_files) = local_files {
202            local_files
203        } else {
204            error!("Could not get local save files");
205            app.send_error_toast("Could not get local save files", None);
206            vec![]
207        };
208        if save_file_index >= local_files.len() {
209            error!("Cannot load save file: No such file");
210            app.send_error_toast("Cannot load save file: No such file", None);
211            return Ok(());
212        }
213        let save_file_name = local_files[save_file_index].clone();
214        info!("🚀 Loading save file: {}", save_file_name);
215        let board_data = get_local_kanban_state(save_file_name.clone(), false, &app.config);
216        match board_data {
217            Ok(boards) => {
218                app.boards.set_boards(boards);
219                app.action_history_manager.reset();
220                info!("👍 Save file {:?} loaded", save_file_name);
221                app.send_info_toast(&format!("👍 Save file {:?} loaded", save_file_name), None);
222            }
223            Err(err) => {
224                debug!("Cannot load save file: {:?}", err);
225                app.send_error_toast("Cannot load save file", None);
226            }
227        }
228        app.dispatch(IoEvent::ResetVisibleBoardsandCards).await;
229        app.set_view(default_view);
230        Ok(())
231    }
232
233    async fn delete_local_save_file(&mut self) -> Result<()> {
234        let mut app = self.app.lock().await;
235        let file_list = get_available_local_save_files(&app.config);
236        let file_list = if let Some(file_list) = file_list {
237            file_list
238        } else {
239            error!("Cannot delete save file: no save files found");
240            app.send_error_toast("Cannot delete save file: no save files found", None);
241            return Ok(());
242        };
243        if app.state.app_list_states.load_save.selected().is_none() {
244            error!("Cannot delete save file: no save file selected");
245            app.send_error_toast("Cannot delete save file: no save file selected", None);
246            return Ok(());
247        }
248        let selected = app.state.app_list_states.load_save.selected().unwrap_or(0);
249        if selected >= file_list.len() {
250            debug!("Cannot delete save file: index out of range");
251            app.send_error_toast("Cannot delete save file: Something went wrong", None);
252            return Ok(());
253        }
254        let file_name = file_list[selected].clone();
255        info!("🚀 Deleting save file: {}", file_name);
256        let path = app.config.save_directory.join(file_name);
257        if !Path::new(&path).exists() {
258            error!("Cannot delete save file: file not found");
259            app.send_error_toast("Cannot delete save file: file not found", None);
260            return Ok(());
261        } else if let Err(err) = std::fs::remove_file(&path) {
262            debug!("Cannot delete save file: {:?}", err);
263            app.send_error_toast("Cannot delete save file: Something went wrong", None);
264            app.state.app_list_states.load_save = ListState::default();
265            return Ok(());
266        } else {
267            info!("👍 Save file deleted");
268            app.send_info_toast("👍 Save file deleted", None);
269        }
270        let file_list = get_available_local_save_files(&app.config);
271        let file_list = if let Some(file_list) = file_list {
272            file_list
273        } else {
274            app.state.app_list_states.load_save = ListState::default();
275            return Ok(());
276        };
277        if selected >= file_list.len() {
278            if file_list.is_empty() {
279                app.state.app_list_states.load_save = ListState::default();
280            } else {
281                app.state
282                    .app_list_states
283                    .load_save
284                    .select(Some(file_list.len() - 1));
285            }
286        }
287        Ok(())
288    }
289
290    async fn refresh_visible_boards_and_cards(&mut self) -> Result<()> {
291        let mut app = self.app.lock().await;
292        refresh_visible_boards_and_cards(&mut app);
293        Ok(())
294    }
295
296    async fn auto_save(&mut self) -> Result<()> {
297        let mut app = self.app.lock().await;
298        match auto_save(&mut app).await {
299            Ok(_) => Ok(()),
300            Err(err) => Err(anyhow!(err)),
301        }
302    }
303
304    async fn load_local_preview(&mut self) -> Result<()> {
305        let mut app = self.app.lock().await;
306        if app.state.app_list_states.load_save.selected().is_none() {
307            return Ok(());
308        }
309        app.preview_boards_and_cards = None;
310
311        let save_file_index = app.state.app_list_states.load_save.selected().unwrap_or(0);
312        let local_files = get_available_local_save_files(&app.config);
313        let local_files = if let Some(local_files) = local_files {
314            local_files
315        } else {
316            error!("Could not get local save files");
317            app.send_error_toast("Could not get local save files", None);
318            vec![]
319        };
320        if save_file_index >= local_files.len() {
321            error!("Cannot load preview: No such file");
322            app.send_error_toast("Cannot load preview: No such file", None);
323            return Ok(());
324        }
325        let save_file_name = local_files[save_file_index].clone();
326        let board_data = get_local_kanban_state(save_file_name.clone(), true, &app.config);
327        match board_data {
328            Ok(boards) => {
329                app.preview_boards_and_cards = Some(boards);
330                let mut visible_boards_and_cards: LinkedHashMap<(u64, u64), Vec<(u64, u64)>> =
331                    LinkedHashMap::new();
332                for (counter, board) in app
333                    .preview_boards_and_cards
334                    .as_ref()
335                    .unwrap()
336                    .get_boards()
337                    .iter()
338                    .enumerate()
339                {
340                    if counter >= app.config.no_of_boards_to_show.into() {
341                        break;
342                    }
343                    let mut visible_cards: Vec<(u64, u64)> = Vec::new();
344                    if board.cards.len() > app.config.no_of_cards_to_show.into() {
345                        for card in board
346                            .cards
347                            .get_all_cards()
348                            .iter()
349                            .take(app.config.no_of_cards_to_show.into())
350                        {
351                            visible_cards.push(card.id);
352                        }
353                    } else {
354                        for card in board.cards.get_all_cards() {
355                            visible_cards.push(card.id);
356                        }
357                    }
358
359                    let mut visible_board: LinkedHashMap<(u64, u64), Vec<(u64, u64)>> =
360                        LinkedHashMap::new();
361                    visible_board.insert(board.id, visible_cards);
362                    visible_boards_and_cards.extend(visible_board);
363                }
364                app.state.preview_visible_boards_and_cards = visible_boards_and_cards;
365                app.state.preview_file_name = Some(save_file_name);
366            }
367            Err(e) => {
368                error!("Error loading preview: {}", e);
369                app.send_error_toast("Error loading preview", None);
370            }
371        }
372        Ok(())
373    }
374
375    async fn cloud_login(&mut self, email_id: String, password: String) -> Result<()> {
376        {
377            let mut app = self.app.lock().await;
378            if app.state.user_login_data.auth_token.is_some() {
379                error!("Already logged in, Please logout first");
380                app.send_error_toast("Already logged in, Please logout first", None);
381                return Ok(());
382            } else {
383                info!("Logging in, please wait...");
384                app.send_info_toast("Logging in, please wait...", None);
385            }
386            if email_id.is_empty() {
387                error!("Email cannot be empty");
388                app.send_error_toast("Email cannot be empty", None);
389                return Ok(());
390            } else if password.is_empty() {
391                error!("Password cannot be empty");
392                app.send_error_toast("Password cannot be empty", None);
393                return Ok(());
394            }
395        }
396
397        let login_for_user_status = login_for_user(&email_id, &password, false).await;
398        if let Err(err) = login_for_user_status {
399            debug!("Error logging in: {:?}", err);
400            let mut app = self.app.lock().await;
401            if err == "Error logging in: \"Invalid login credentials\"" {
402                error!("Invalid login credentials");
403                app.send_error_toast("Invalid login credentials", None);
404            } else {
405                error!("Error logging in");
406                app.send_error_toast("Error logging in", None);
407            }
408            return Ok(());
409        }
410        let (access_token, user_id, refresh_token) = login_for_user_status.unwrap();
411        let mut app = self.app.lock().await;
412        app.state.user_login_data.auth_token = Some(access_token.to_string());
413        app.state.user_login_data.refresh_token = Some(refresh_token.to_string());
414        app.state.user_login_data.email_id = Some(email_id.to_string());
415        app.state.user_login_data.user_id = Some(user_id.to_string());
416        app.main_menu.logged_in = true;
417
418        if app.config.auto_login {
419            save_refresh_token_to_disk(
420                &refresh_token,
421                &email_id,
422                app.state.encryption_key_from_arguments.clone(),
423            )
424            .await?;
425        }
426
427        if app.state.current_view == View::Login {
428            handle_go_to_previous_view(&mut app).await;
429        }
430
431        info!("👍 Logged in");
432        app.send_info_toast("👍 Logged in", None);
433
434        Ok(())
435    }
436
437    async fn cloud_logout(&mut self) -> Result<()> {
438        {
439            let mut app = self.app.lock().await;
440            if app.state.user_login_data.auth_token.is_none() {
441                error!("Not logged in");
442                app.send_error_toast("Not logged in", None);
443                return Ok(());
444            } else {
445                info!("Logging out, please wait...");
446                app.send_info_toast("Logging out, please wait...", None);
447            }
448        }
449        let client = reqwest::Client::new();
450        let response = client
451            .post(format!("{}/auth/v1/logout", SUPABASE_URL))
452            .header("apikey", SUPABASE_ANON_KEY)
453            .header("Content-Type", "application/json")
454            .header(
455                "Authorization",
456                format!(
457                    "Bearer {}",
458                    self.app
459                        .lock()
460                        .await
461                        .state
462                        .user_login_data
463                        .auth_token
464                        .as_ref()
465                        .unwrap()
466                ),
467            )
468            .send()
469            .await?;
470
471        let status = response.status();
472        if status == StatusCode::NO_CONTENT {
473            let mut app = self.app.lock().await;
474            app.state.user_login_data = UserLoginData::default();
475            app.main_menu.logged_in = false;
476            info!("👍 Logged out");
477            app.send_info_toast("👍 Logged out", None);
478        } else {
479            error!("Error logging out");
480            let mut app = self.app.lock().await;
481            app.send_error_toast("Error logging out", None);
482        }
483        delete_refresh_token_from_disk().await?;
484        Ok(())
485    }
486
487    async fn cloud_signup(
488        &mut self,
489        email_id: String,
490        password: String,
491        confirm_password: String,
492    ) -> Result<()> {
493        {
494            let mut app = self.app.lock().await;
495            if app.state.user_login_data.auth_token.is_some() {
496                error!("Already logged in, Please logout first");
497                app.send_error_toast("Already logged in, Please logout first", None);
498                return Ok(());
499            }
500            if email_id.is_empty() {
501                error!("Email cannot be empty");
502                app.send_error_toast("Email cannot be empty", None);
503                return Ok(());
504            }
505            if password.is_empty() || confirm_password.is_empty() {
506                error!("Password cannot be empty");
507                app.send_error_toast("Password cannot be empty", None);
508                return Ok(());
509            }
510            if password != confirm_password {
511                error!("Passwords do not match");
512                app.send_error_toast("Passwords do not match", None);
513                return Ok(());
514            }
515            let email_regex =
516                regex::Regex::new(EMAIL_REGEX).expect("Invalid email regex in constants");
517            if !email_regex.is_match(&email_id) {
518                error!("Invalid email format");
519                app.send_error_toast("Invalid email format", None);
520                return Ok(());
521            }
522
523            let password_status = check_for_safe_password(&password);
524            match password_status {
525                PasswordStatus::Strong => {}
526                PasswordStatus::MissingLowercase => {
527                    error!("Password must contain at least one lowercase character");
528                    app.send_error_toast(
529                        "Password must contain at least one lowercase character",
530                        None,
531                    );
532                    return Ok(());
533                }
534                PasswordStatus::MissingUppercase => {
535                    error!("Password must contain at least one uppercase character");
536                    app.send_error_toast(
537                        "Password must contain at least one uppercase character",
538                        None,
539                    );
540                    return Ok(());
541                }
542                PasswordStatus::MissingNumber => {
543                    error!("Password must contain at least one number");
544                    app.send_error_toast("Password must contain at least one number", None);
545                    return Ok(());
546                }
547                PasswordStatus::MissingSpecialChar => {
548                    error!("Password must contain at least one special character");
549                    app.send_error_toast(
550                        "Password must contain at least one special character",
551                        None,
552                    );
553                    return Ok(());
554                }
555                PasswordStatus::TooShort => {
556                    error!(
557                        "Password must be at least {} characters long",
558                        MIN_PASSWORD_LENGTH
559                    );
560                    app.send_error_toast(
561                        &format!(
562                            "Password must be at least {} characters long",
563                            MIN_PASSWORD_LENGTH
564                        ),
565                        None,
566                    );
567                    return Ok(());
568                }
569                PasswordStatus::TooLong => {
570                    error!(
571                        "Password must be at most {} characters long",
572                        MAX_PASSWORD_LENGTH
573                    );
574                    app.send_error_toast(
575                        &format!(
576                            "Password must be at most {} characters long",
577                            MAX_PASSWORD_LENGTH
578                        ),
579                        None,
580                    );
581                }
582            }
583
584            // check if encryption key is present
585            if get_user_encryption_key(app.state.encryption_key_from_arguments.clone()).is_ok() {
586                warn!("Encryption key already exists, please delete it first or move it if you are trying to create a second account");
587                app.send_warning_toast("Encryption key already exists, please delete it first or move it if you are trying to create a second account", Some(Duration::from_secs(10)));
588                return Ok(());
589            }
590
591            info!("Signing up, please wait...");
592            app.send_info_toast("Signing up, please wait...", None);
593        }
594
595        let request_body = json!(
596            {
597                "email": email_id,
598                "password": password
599            }
600        );
601        let client = reqwest::Client::new();
602        let response = client
603            .post(format!("{}/auth/v1/signup", SUPABASE_URL))
604            .header("apikey", SUPABASE_ANON_KEY)
605            .header("Content-Type", "application/json")
606            .body(request_body.to_string())
607            .send()
608            .await?;
609        let status = response.status();
610        let body = response.json::<serde_json::Value>().await;
611        if status == StatusCode::OK {
612            match body {
613                Ok(body) => {
614                    let confirmation_sent = body.get("confirmation_sent_at");
615                    match confirmation_sent {
616                        Some(confirmation_sent) => {
617                            let confirmation_sent = confirmation_sent.as_str();
618                            if confirmation_sent.is_none() {
619                                error!("Error signing up");
620                                let mut app = self.app.lock().await;
621                                app.send_error_toast("Error signing up", None);
622                                return Ok(());
623                            }
624                            info!("👍 Confirmation email sent");
625                            let mut app = self.app.lock().await;
626                            app.send_info_toast(
627                                "👍 Confirmation email sent",
628                                Some(Duration::from_secs(10)),
629                            );
630                            let key = generate_new_encryption_key();
631                            let save_result = save_user_encryption_key(&key);
632                            if save_result.is_err() {
633                                error!("Error saving encryption key");
634                                debug!("Error saving encryption key: {:?}", save_result);
635                                app.send_error_toast("Error saving encryption key", None);
636                                return Ok(());
637                            } else {
638                                let save_path = save_result.unwrap();
639                                info!("👍 Encryption key saved at {}", save_path);
640                                app.send_info_toast(
641                                    &format!("👍 Encryption key saved at {}", save_path),
642                                    Some(Duration::from_secs(10)),
643                                );
644                                warn!("Please keep this key safe, you will need it to decrypt your data, you will not be able to recover your data without it");
645                                app.send_warning_toast(
646                                    "Please keep this key safe, you will need it to decrypt your data, you will not be able to recover your data without it",
647                                    Some(Duration::from_secs(10)),
648                                );
649                                let default_view = app.config.default_view;
650                                app.set_view(default_view);
651                            }
652                        }
653                        None => {
654                            error!("Error signing up");
655                            let mut app = self.app.lock().await;
656                            app.send_error_toast("Error signing up", None);
657                        }
658                    }
659                }
660                Err(e) => {
661                    error!("Error signing up: {}", e);
662                    let mut app = self.app.lock().await;
663                    app.send_error_toast("Error signing up", None);
664                }
665            }
666        } else if status == StatusCode::TOO_MANY_REQUESTS {
667            error!("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢");
668            debug!("status code {}, response body: {:?}", status, body);
669            let mut app = self.app.lock().await;
670            app.send_error_toast("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢", None);
671        } else {
672            error!("Error signing up");
673            debug!("status code {}, response body: {:?}", status, body);
674            let mut app = self.app.lock().await;
675            app.send_error_toast("Error signing up", None);
676        }
677        Ok(())
678    }
679
680    async fn send_reset_password_email(&mut self, email_id: String) -> Result<()> {
681        {
682            let mut app = self.app.lock().await;
683            if let Some(reset_time) = app.state.last_reset_password_link_sent_time {
684                if reset_time.elapsed() < Duration::from_secs(MIN_TIME_BETWEEN_SENDING_RESET_LINK) {
685                    let remaining_time = Duration::from_secs(MIN_TIME_BETWEEN_SENDING_RESET_LINK)
686                        .checked_sub(reset_time.elapsed())
687                        .unwrap();
688                    error!(
689                        "Please wait for {} seconds before sending another reset password email",
690                        remaining_time.as_secs()
691                    );
692                    app.send_error_toast(
693                        &format!(
694                        "Please wait for {} seconds before sending another reset password email",
695                        remaining_time.as_secs()
696                    ),
697                        None,
698                    );
699                    return Ok(());
700                }
701            }
702            if email_id.is_empty() {
703                error!("Email cannot be empty");
704                app.send_error_toast("Email cannot be empty", None);
705                return Ok(());
706            } else {
707                info!("Sending reset password email, please wait...");
708                app.send_info_toast("Sending reset password email, please wait...", None);
709            }
710        }
711
712        let request_body = json!({ "email": email_id });
713
714        let client = reqwest::Client::new();
715        let response = client
716            .post(format!("{}/auth/v1/recover", SUPABASE_URL))
717            .header("apikey", SUPABASE_ANON_KEY)
718            .header("Content-Type", "application/json")
719            .body(request_body.to_string())
720            .send()
721            .await?;
722
723        let status = response.status();
724        if status == StatusCode::OK {
725            info!("👍 Reset password email sent");
726            let mut app = self.app.lock().await;
727            app.state.last_reset_password_link_sent_time = Some(Instant::now());
728            app.send_info_toast("👍 Reset password email sent", None);
729        } else if status == StatusCode::TOO_MANY_REQUESTS {
730            let body = response.json::<serde_json::Value>().await;
731            error!("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢");
732            debug!("status code {}, response body: {:?}", status, body);
733            let mut app = self.app.lock().await;
734            app.send_error_toast("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢", None);
735        } else {
736            error!("Error sending reset password email");
737            let mut app = self.app.lock().await;
738            app.send_error_toast("Error sending reset password email", None);
739        }
740        Ok(())
741    }
742
743    async fn reset_password(
744        &mut self,
745        reset_link: String,
746        new_password: String,
747        confirm_password: String,
748    ) -> Result<()> {
749        {
750            let mut app = self.app.lock().await;
751            if reset_link.is_empty() {
752                error!("Reset link cannot be empty");
753                app.send_error_toast("Reset link cannot be empty", None);
754                return Ok(());
755            }
756            if new_password.is_empty() || confirm_password.is_empty() {
757                error!("Password cannot be empty");
758                app.send_error_toast("Password cannot be empty", None);
759                return Ok(());
760            }
761            if new_password != confirm_password {
762                error!("Passwords do not match");
763                app.send_error_toast("Passwords do not match", None);
764                return Ok(());
765            }
766            let password_status = check_for_safe_password(&new_password);
767            match password_status {
768                PasswordStatus::Strong => {}
769                PasswordStatus::MissingLowercase => {
770                    error!("Password must contain at least one lowercase character");
771                    app.send_error_toast(
772                        "Password must contain at least one lowercase character",
773                        None,
774                    );
775                    return Ok(());
776                }
777                PasswordStatus::MissingUppercase => {
778                    error!("Password must contain at least one uppercase character");
779                    app.send_error_toast(
780                        "Password must contain at least one uppercase character",
781                        None,
782                    );
783                    return Ok(());
784                }
785                PasswordStatus::MissingNumber => {
786                    error!("Password must contain at least one number");
787                    app.send_error_toast("Password must contain at least one number", None);
788                    return Ok(());
789                }
790                PasswordStatus::MissingSpecialChar => {
791                    error!("Password must contain at least one special character");
792                    app.send_error_toast(
793                        "Password must contain at least one special character",
794                        None,
795                    );
796                    return Ok(());
797                }
798                PasswordStatus::TooShort => {
799                    error!(
800                        "Password must be at least {} characters long",
801                        MIN_PASSWORD_LENGTH
802                    );
803                    app.send_error_toast(
804                        &format!(
805                            "Password must be at least {} characters long",
806                            MIN_PASSWORD_LENGTH
807                        ),
808                        None,
809                    );
810                    return Ok(());
811                }
812                PasswordStatus::TooLong => {
813                    error!(
814                        "Password must be at most {} characters long",
815                        MAX_PASSWORD_LENGTH
816                    );
817                    app.send_error_toast(
818                        &format!(
819                            "Password must be at most {} characters long",
820                            MAX_PASSWORD_LENGTH
821                        ),
822                        None,
823                    );
824                }
825            }
826
827            info!("Resetting password, please wait...");
828            app.send_info_toast("Resetting password, please wait...", None);
829        }
830
831        let client = reqwest::Client::new();
832        let response = client.get(reset_link).send().await;
833        match response {
834            Ok(_) => {
835                error!("Error verifying reset password link");
836                let mut app = self.app.lock().await;
837                app.send_error_toast("Error verifying reset password link", None);
838            }
839            Err(e) => {
840                let mut app = self.app.lock().await;
841                let error_url = e.url();
842                if error_url.is_none() {
843                    error!("Error verifying reset password link");
844                    app.send_error_toast("Error verifying reset password link", None);
845                    return Ok(());
846                }
847                let error_url = error_url.unwrap();
848                let error_url = error_url.to_string();
849                debug!("error url: {}", error_url);
850                let access_token = error_url.split("access_token=");
851                let access_token = access_token.last();
852                if access_token.is_none() {
853                    error!("Error verifying reset password link");
854                    app.send_error_toast("Error verifying reset password link", None);
855                    return Ok(());
856                }
857                let mut access_token = access_token.unwrap().split("&expires_at");
858                let access_token = access_token.next();
859                debug!("access token: {:?}", access_token);
860                if access_token.is_none() {
861                    error!("Error verifying reset password link");
862                    app.send_error_toast("Error verifying reset password link", None);
863                    return Ok(());
864                }
865                drop(app);
866                let access_token = access_token.unwrap();
867                let request_body = json!({ "password": new_password });
868                let reset_client = reqwest::Client::new();
869                let reset_response = reset_client
870                    .put(format!("{}/auth/v1/user", SUPABASE_URL))
871                    .header("apikey", SUPABASE_ANON_KEY)
872                    .header("Content-Type", "application/json")
873                    .header("Authorization", format!("Bearer {}", access_token))
874                    .body(request_body.to_string())
875                    .send()
876                    .await?;
877
878                let status = reset_response.status();
879                let mut app = self.app.lock().await;
880                match status {
881                    StatusCode::OK => {
882                        info!("👍 Password reset successful");
883                        if app.state.current_view == View::ResetPassword {
884                            handle_go_to_previous_view(&mut app).await;
885                        }
886                        app.send_info_toast("👍 Password reset successful", None);
887                    }
888                    StatusCode::UNPROCESSABLE_ENTITY => {
889                        error!(
890                            "Error resetting password, new password cannot be same as old password"
891                        );
892                        debug!(
893                            "Error resetting password: {:?}",
894                            reset_response.text().await
895                        );
896                        app.send_error_toast(
897                            "Error resetting password, new password cannot be same as old password",
898                            None,
899                        );
900                    }
901                    _ => {
902                        error!("Error resetting password");
903                        debug!(
904                            "Error resetting password: {:?}",
905                            reset_response.text().await
906                        );
907                        app.send_error_toast("Error resetting password", None);
908                    }
909                }
910            }
911        }
912        Ok(())
913    }
914
915    async fn sync_local_data(&mut self) -> Result<()> {
916        {
917            let mut app = self.app.lock().await;
918            if app.state.user_login_data.auth_token.is_none() {
919                error!("Not logged in");
920                app.send_error_toast("Not logged in", None);
921                return Ok(());
922            } else {
923                info!("Syncing local data, please wait...");
924                app.send_info_toast("Syncing local data, please wait...", None);
925            }
926        }
927
928        let save_ids = self.get_save_ids_for_user().await?;
929        let max_save_id = save_ids.iter().max();
930        let max_save_id = if let Some(max_save_id) = max_save_id {
931            max_save_id + 1
932        } else {
933            0
934        };
935
936        let mut app = self.app.lock().await;
937        let key = get_user_encryption_key(app.state.encryption_key_from_arguments.clone());
938        if key.is_err() {
939            error!("Error syncing local data, Could not get encryption key, If you have lost it please generate a new one using the -g flag");
940            debug!(
941                "Error syncing local data: {:?}, Could not get encryption key",
942                key.err()
943            );
944            app.send_error_toast("Error syncing local data, Could not get encryption key, If you have lost it please generate a new one using the -g flag", None);
945            return Ok(());
946        }
947        let key = key.unwrap();
948        let encrypt_result = encrypt_save(&app.boards, &key);
949        if encrypt_result.is_err() {
950            error!("Error syncing local data");
951            debug!(
952                "Error syncing local data: {:?}, could not encrypt",
953                encrypt_result.err()
954            );
955            app.send_error_toast("Error syncing local data", None);
956            return Ok(());
957        }
958        let (encrypted_board_data, nonce) = encrypt_result.unwrap();
959        let auth_token = app.state.user_login_data.auth_token.clone().unwrap();
960        let user_id = app.state.user_login_data.user_id.clone().unwrap();
961        drop(app);
962        let client = reqwest::Client::new();
963        let response = client
964            .post(format!("{}/rest/v1/user_data", SUPABASE_URL))
965            .header("apikey", SUPABASE_ANON_KEY)
966            .header("Content-Type", "application/json")
967            .header("Authorization", format!("Bearer {}", auth_token))
968            .body(
969                json!(
970                    {
971                        "user_id": user_id,
972                        "board_data": encrypted_board_data,
973                        "save_id": max_save_id,
974                        "nonce": nonce
975                    }
976                )
977                .to_string(),
978            )
979            .send()
980            .await?;
981
982        let status = response.status();
983        let mut app = self.app.lock().await;
984        if status == StatusCode::CREATED {
985            info!("👍 Local data synced to the cloud");
986            app.send_info_toast("👍 Local data synced to the cloud", None);
987            if app.state.cloud_data.is_some() {
988                app.dispatch(IoEvent::GetCloudData).await;
989            }
990        } else {
991            error!("Error syncing local data");
992            debug!("Error syncing local data: {:?}", response.text().await);
993            app.send_error_toast("Error syncing local data", None);
994        }
995        Ok(())
996    }
997
998    async fn get_save_ids_for_user(&mut self) -> Result<Vec<usize>> {
999        {
1000            let mut app = self.app.lock().await;
1001            if app.state.user_login_data.auth_token.is_none() {
1002                error!("Not logged in");
1003                app.send_error_toast("Not logged in", None);
1004                return Ok(vec![]);
1005            }
1006        }
1007
1008        let (user_id, access_token) = {
1009            let app = self.app.lock().await;
1010            let user_id = app.state.user_login_data.user_id.as_ref().unwrap().clone();
1011            let access_token = app
1012                .state
1013                .user_login_data
1014                .auth_token
1015                .as_ref()
1016                .unwrap()
1017                .clone();
1018            (user_id, access_token)
1019        };
1020
1021        let result = get_all_save_ids_for_user(user_id, &access_token).await;
1022        if result.is_err() {
1023            let error_string = format!("{:?}", result.err());
1024            error!("{}", error_string);
1025            let mut app = self.app.lock().await;
1026            app.send_error_toast(&error_string, None);
1027            Ok(vec![])
1028        } else {
1029            let save_ids = result.unwrap();
1030            Ok(save_ids)
1031        }
1032    }
1033
1034    async fn get_cloud_data(&mut self) -> Result<()> {
1035        {
1036            let mut app = self.app.lock().await;
1037            if app.state.user_login_data.auth_token.is_none() {
1038                error!("Not logged in");
1039                app.send_error_toast("Not logged in", None);
1040                return Ok(());
1041            } else {
1042                info!("Refreshing cloud data, please wait...");
1043                app.send_info_toast("Refreshing cloud data, please wait...", None);
1044                app.state.cloud_data = None;
1045            }
1046        }
1047
1048        let app = self.app.lock().await;
1049        let auth_token = app.state.user_login_data.auth_token.clone().unwrap();
1050        drop(app);
1051        let client = reqwest::Client::new();
1052        let response = client
1053            .get(format!("{}/rest/v1/user_data", SUPABASE_URL))
1054            .header("apikey", SUPABASE_ANON_KEY)
1055            .header("Content-Type", "application/json")
1056            .header("Authorization", format!("Bearer {}", auth_token))
1057            .send()
1058            .await?;
1059
1060        let mut app = self.app.lock().await;
1061
1062        let status = response.status();
1063        if status == StatusCode::OK {
1064            let body = response.json::<Vec<CloudData>>().await;
1065            match body {
1066                Ok(cloud_data) => {
1067                    app.state.cloud_data = Some(cloud_data);
1068                    info!("👍 Cloud data loaded");
1069                    app.send_info_toast("👍 Cloud data loaded", None);
1070                }
1071                Err(e) => {
1072                    error!("Error Refreshing cloud data: {}", e);
1073                    app.send_error_toast("Error Refreshing cloud data", None);
1074                }
1075            }
1076        } else {
1077            error!("Error Refreshing cloud data");
1078            app.send_error_toast("Error Refreshing cloud data", None);
1079        }
1080        Ok(())
1081    }
1082
1083    async fn preview_cloud_save(&mut self) -> Result<()> {
1084        {
1085            let mut app = self.app.lock().await;
1086            if app.state.app_list_states.load_save.selected().is_none() {
1087                error!("No save selected to preview");
1088                app.send_error_toast("No save selected to preview", None);
1089                return Ok(());
1090            }
1091        }
1092
1093        let mut app = self.app.lock().await;
1094        let selected_index = app.state.app_list_states.load_save.selected().unwrap();
1095        let cloud_data = app.state.cloud_data.clone();
1096        if cloud_data.is_none() {
1097            debug!("No cloud data preview found to select");
1098            return Ok(());
1099        }
1100        let cloud_data = cloud_data.unwrap();
1101        if selected_index >= cloud_data.len() {
1102            debug!("Selected index is out of bounds");
1103            return Ok(());
1104        }
1105        let save = cloud_data[selected_index].clone();
1106        let key = get_user_encryption_key(app.state.encryption_key_from_arguments.clone());
1107        if key.is_err() {
1108            error!("Error loading save file, Could not get user Encryption key .If lost please generate a new one by using the -g flag");
1109            debug!("Error loading save file: {:?}", key.err());
1110            app.send_error_toast(
1111                "Error loading save file, Could not get user Encryption key .If lost please generate a new one by using the -g flag",
1112                None,
1113            );
1114            return Ok(());
1115        }
1116        let key = key.unwrap();
1117        let decrypt_result = decrypt_save(save.board_data, key.as_slice(), &save.nonce);
1118        if decrypt_result.is_err() {
1119            error!("Error loading save file, Could not decrypt save file. The save file must have been created with a different encryption key, either generate a new one with the -g flag or replace the current encryption key with the one used to create the save file");
1120            debug!("Error loading save file: {:?}", decrypt_result.err());
1121            app.send_error_toast("Error loading save file, Could not decrypt save file. The save file must have been created with a different encryption key, either generate a new one with the -g flag or replace the current encryption key with the one used to create the save file", Some(Duration::from_secs(5)));
1122            return Ok(());
1123        }
1124        app.preview_boards_and_cards = Some(decrypt_result.unwrap());
1125        let mut visible_boards_and_cards: LinkedHashMap<(u64, u64), Vec<(u64, u64)>> =
1126            LinkedHashMap::new();
1127        for (counter, board) in app
1128            .preview_boards_and_cards
1129            .as_ref()
1130            .unwrap()
1131            .get_boards()
1132            .iter()
1133            .enumerate()
1134        {
1135            if counter >= app.config.no_of_boards_to_show.into() {
1136                break;
1137            }
1138            let mut visible_cards: Vec<(u64, u64)> = Vec::new();
1139            if board.cards.len() > app.config.no_of_cards_to_show.into() {
1140                for card in board
1141                    .cards
1142                    .get_all_cards()
1143                    .iter()
1144                    .take(app.config.no_of_cards_to_show.into())
1145                {
1146                    visible_cards.push(card.id);
1147                }
1148            } else {
1149                for card in board.cards.get_all_cards() {
1150                    visible_cards.push(card.id);
1151                }
1152            }
1153
1154            let mut visible_board: LinkedHashMap<(u64, u64), Vec<(u64, u64)>> =
1155                LinkedHashMap::new();
1156            visible_board.insert(board.id, visible_cards);
1157            visible_boards_and_cards.extend(visible_board);
1158        }
1159        let save_timestamp = save.created_at.split('.').next();
1160        if save_timestamp.is_none() {
1161            debug!("Error splitting {}", save.created_at);
1162            app.state.preview_visible_boards_and_cards = visible_boards_and_cards;
1163            app.state.preview_file_name = Some(format!("Cloud_save_{}", save.save_id,));
1164            return Ok(());
1165        }
1166        let save_timestamp = save_timestamp.unwrap();
1167        let save_date = NaiveDateTime::parse_from_str(save_timestamp, "%Y-%m-%dT%H:%M:%S");
1168        if save_date.is_ok() {
1169            let save_date = save_date.unwrap();
1170            let save_date = save_date.format(app.config.date_time_format.to_parser_string());
1171            app.state.preview_file_name =
1172                Some(format!("Cloud_save_{} - {}", save.save_id, save_date));
1173        } else {
1174            debug!("Error parsing save date {}", save.created_at);
1175        }
1176        app.state.preview_visible_boards_and_cards = visible_boards_and_cards;
1177        Ok(())
1178    }
1179
1180    async fn load_save_file_cloud(&mut self) -> Result<()> {
1181        let mut app = self.app.lock().await;
1182        let default_view = app.config.default_view;
1183        let save_file_index = app.state.app_list_states.load_save.selected().unwrap_or(0);
1184        let cloud_saves = app.state.cloud_data.clone();
1185        let local_files = if let Some(cloud_saves) = cloud_saves {
1186            cloud_saves
1187        } else {
1188            error!("Could not get local save files");
1189            app.send_error_toast("Could not get local save files", None);
1190            vec![]
1191        };
1192        if save_file_index >= local_files.len() {
1193            error!("Cannot load save file: No such file");
1194            app.send_error_toast("Cannot load save file: No such file", None);
1195            return Ok(());
1196        }
1197        let save_file_number = local_files[save_file_index].save_id;
1198        info!("🚀 Loading save file: cloud_save_{}", save_file_number);
1199        let encrypted_board_data = &local_files[save_file_index].board_data;
1200        let key = get_user_encryption_key(app.state.encryption_key_from_arguments.clone());
1201        if key.is_err() {
1202            error!("Error loading save file, Could not get user Encryption key. If lost please generate a new one by using the -g flag");
1203            debug!("Error loading save file: {:?}", key.err());
1204            app.send_error_toast(
1205                "Error loading save file, Could not get user Encryption key. If lost please generate a new one by using the -g flag",
1206                None,
1207            );
1208            return Ok(());
1209        }
1210        let key = key.unwrap();
1211        let decrypt_result = decrypt_save(
1212            encrypted_board_data.to_string(),
1213            key.as_slice(),
1214            &local_files[save_file_index].nonce,
1215        );
1216        if decrypt_result.is_err() {
1217            error!("Error loading save file, Could not decrypt save file. The save file must have been created with a different encryption key, either generate a new one with the -g flag or replace the current encryption key with the one used to create the save file");
1218            debug!("Error loading save file: {:?}", decrypt_result.err());
1219            app.send_error_toast("Error loading save file, Could not decrypt save file. The save file must have been created with a different encryption key, either generate a new one with the -g flag or replace the current encryption key with the one used to create the save file", Some(Duration::from_secs(5)));
1220            return Ok(());
1221        }
1222        let decrypt_result = decrypt_result.unwrap();
1223        app.boards.set_boards(decrypt_result);
1224        info!("👍 Save file cloud_save_{} loaded", save_file_number);
1225        app.send_info_toast(
1226            &format!("👍 Save file cloud_save_{} loaded", save_file_number),
1227            None,
1228        );
1229        app.dispatch(IoEvent::ResetVisibleBoardsandCards).await;
1230        app.set_view(default_view);
1231        Ok(())
1232    }
1233
1234    async fn delete_cloud_save(&mut self) -> Result<()> {
1235        {
1236            let mut app = self.app.lock().await;
1237            if app.state.user_login_data.auth_token.is_none() {
1238                error!("Not logged in");
1239                app.send_error_toast("Not logged in", None);
1240                return Ok(());
1241            } else {
1242                info!("Deleting cloud save, please wait...");
1243                app.send_info_toast("Deleting cloud save, please wait...", None);
1244            }
1245        }
1246
1247        let mut app = self.app.lock().await;
1248        let save_file_index = app.state.app_list_states.load_save.selected().unwrap_or(0);
1249        let user_access_token = app.state.user_login_data.auth_token.clone().unwrap();
1250        let cloud_saves = app.state.cloud_data.clone();
1251        let cloud_saves = if let Some(cloud_saves) = cloud_saves {
1252            cloud_saves
1253        } else {
1254            error!("Could not get local save files");
1255            app.send_error_toast("Could not get local save files", None);
1256            return Ok(());
1257        };
1258        if save_file_index >= cloud_saves.len() {
1259            error!("Cannot delete save file: No such file");
1260            app.send_error_toast("Cannot delete save file: No such file", None);
1261            return Ok(());
1262        }
1263        drop(app);
1264        let save_file_id = cloud_saves[save_file_index].id;
1265        let save_number = cloud_saves[save_file_index].save_id;
1266        let delete_status =
1267            delete_a_save_from_database(&user_access_token, false, save_file_id, Some(save_number))
1268                .await;
1269        let mut app = self.app.lock().await;
1270        if delete_status.is_err() {
1271            app.send_error_toast("Error deleting cloud save", None);
1272        } else {
1273            app.send_info_toast(
1274                &format!("👍 Cloud save cloud_save_{} deleted", save_number),
1275                None,
1276            );
1277        }
1278
1279        Ok(())
1280    }
1281}
1282
1283pub(crate) fn get_config_dir() -> Result<PathBuf, String> {
1284    let home_dir = home::home_dir();
1285    if home_dir.is_none() {
1286        return Err(String::from("Error getting home directory"));
1287    }
1288    let mut config_dir = home_dir.unwrap();
1289    if cfg!(windows) {
1290        config_dir.push("AppData");
1291        config_dir.push("Roaming");
1292    } else {
1293        config_dir.push(".config");
1294    }
1295    config_dir.push(CONFIG_DIR_NAME);
1296    Ok(config_dir)
1297}
1298
1299pub(crate) fn get_save_dir() -> PathBuf {
1300    let mut save_dir = env::temp_dir();
1301    save_dir.push(SAVE_DIR_NAME);
1302    save_dir
1303}
1304
1305pub fn prepare_config_dir() -> Result<(), String> {
1306    let config_dir = get_config_dir();
1307    if config_dir.is_err() {
1308        return Err(String::from("Error getting config directory"));
1309    }
1310    let config_dir = config_dir.unwrap();
1311    if !config_dir.exists() {
1312        let dir_creation_status = std::fs::create_dir_all(&config_dir);
1313        if dir_creation_status.is_err() {
1314            return Err(String::from("Error creating config directory"));
1315        }
1316    }
1317    let mut config_file = config_dir;
1318    config_file.push(CONFIG_FILE_NAME);
1319    if !config_file.exists() {
1320        let default_config = AppConfig::default();
1321        let config_json = serde_json::to_string_pretty(&default_config);
1322        if let Ok(config_json) = config_json {
1323            let file_creation_status = std::fs::write(&config_file, config_json);
1324            if file_creation_status.is_err() {
1325                return Err(String::from("Error creating config file"));
1326            }
1327        } else {
1328            return Err(String::from("Error creating config file"));
1329        }
1330    }
1331    Ok(())
1332}
1333
1334fn prepare_save_dir() -> bool {
1335    let save_dir = get_save_dir();
1336    if !save_dir.exists() {
1337        std::fs::create_dir_all(&save_dir).unwrap();
1338    }
1339    true
1340}
1341
1342fn prepare_boards(app: &mut App) {
1343    let boards = if app.config.always_load_last_save {
1344        let latest_save_file_info = get_latest_save_file(&app.config);
1345        if let Ok(latest_save_file) = latest_save_file_info {
1346            let local_data = get_local_kanban_state(latest_save_file.clone(), false, &app.config);
1347            match local_data {
1348                Ok(data) => {
1349                    info!("👍 Local data loaded from {:?}", latest_save_file);
1350                    app.send_info_toast(
1351                        &format!("👍 Local data loaded from {:?}", latest_save_file),
1352                        None,
1353                    );
1354                    data
1355                }
1356                Err(err) => {
1357                    debug!("Cannot get local data: {:?}", err);
1358                    error!("👎 Cannot get local data, Data might be corrupted or is not in the correct format");
1359                    app.send_error_toast("👎 Cannot get local data, Data might be corrupted or is not in the correct format", None);
1360                    Boards::default()
1361                }
1362            }
1363        } else {
1364            Boards::default()
1365        }
1366    } else {
1367        app.set_view(View::LoadLocalSave);
1368        Boards::default()
1369    };
1370    app.boards.set_boards(boards);
1371}
1372
1373fn get_latest_save_file(config: &AppConfig) -> Result<String, String> {
1374    let local_save_files = get_available_local_save_files(config);
1375    let local_save_files = if let Some(local_save_files) = local_save_files {
1376        local_save_files
1377    } else {
1378        return Err("No local save files found".to_string());
1379    };
1380    let fall_back_version = -1;
1381    if local_save_files.is_empty() {
1382        return Err("No local save files found".to_string());
1383    }
1384
1385    let latest_date = local_save_files
1386        .iter()
1387        .map(|file| {
1388            let date = file.split('_').collect::<Vec<&str>>()[1];
1389            NaiveDate::parse_from_str(date, "%d-%m-%Y").unwrap()
1390        })
1391        .max()
1392        .unwrap();
1393
1394    let latest_version = local_save_files
1395        .iter()
1396        .filter(|file| {
1397            let date = file.split('_').collect::<Vec<&str>>()[1];
1398            NaiveDate::parse_from_str(date, "%d-%m-%Y").unwrap() == latest_date
1399        })
1400        .map(|file| {
1401            let version = file.split("_v").collect::<Vec<&str>>()[1];
1402            let version = version.split('.').collect::<Vec<&str>>()[0];
1403            version.parse::<i32>().unwrap_or(fall_back_version)
1404        })
1405        .max()
1406        .unwrap_or(fall_back_version);
1407
1408    if latest_version == fall_back_version {
1409        return Err("No local save files found".to_string());
1410    }
1411    let latest_version = latest_version as u32;
1412
1413    let latest_save_file = format!(
1414        "kanban_{}_v{}.json",
1415        latest_date.format("%d-%m-%Y"),
1416        latest_version
1417    );
1418    Ok(latest_save_file)
1419}
1420
1421pub fn refresh_visible_boards_and_cards(app: &mut App) {
1422    let mut visible_boards_and_cards: LinkedHashMap<(u64, u64), Vec<(u64, u64)>> =
1423        LinkedHashMap::new();
1424    let boards = if app.filtered_boards.is_empty() {
1425        app.boards.get_boards()
1426    } else {
1427        app.filtered_boards.get_boards()
1428    };
1429    for (i, board) in boards.iter().enumerate() {
1430        if (i) as u16 == app.config.no_of_boards_to_show {
1431            break;
1432        }
1433        let mut visible_cards: Vec<(u64, u64)> = Vec::new();
1434        if board.cards.len() > app.config.no_of_cards_to_show.into() {
1435            for card in board
1436                .cards
1437                .get_all_cards()
1438                .iter()
1439                .take(app.config.no_of_cards_to_show.into())
1440            {
1441                visible_cards.push(card.id);
1442            }
1443        } else {
1444            for card in board.cards.get_all_cards() {
1445                visible_cards.push(card.id);
1446            }
1447        }
1448
1449        let mut visible_board: LinkedHashMap<(u64, u64), Vec<(u64, u64)>> = LinkedHashMap::new();
1450        visible_board.insert(board.id, visible_cards);
1451        visible_boards_and_cards.extend(visible_board);
1452    }
1453    app.visible_boards_and_cards = visible_boards_and_cards;
1454    if !app.visible_boards_and_cards.is_empty() {
1455        app.state.current_board_id = Some(*app.visible_boards_and_cards.keys().next().unwrap());
1456        if !app
1457            .visible_boards_and_cards
1458            .values()
1459            .next()
1460            .unwrap()
1461            .is_empty()
1462        {
1463            app.state.current_card_id =
1464                Some(app.visible_boards_and_cards.values().next().unwrap()[0]);
1465        }
1466    }
1467}
1468
1469pub fn make_file_system_safe_name(name: &str) -> String {
1470    let mut safe_name = name.to_string();
1471    let unsafe_chars = vec!["/", "\\", ":", "*", "?", "\"", "<", ">", "|", " "];
1472    for unsafe_char in unsafe_chars {
1473        safe_name = safe_name.replace(unsafe_char, "");
1474    }
1475    safe_name
1476}
1477
1478pub async fn auto_save(app: &mut App<'_>) -> Result<(), String> {
1479    if save_required(app) {
1480        save_kanban_state_locally(app.boards.get_boards().to_vec(), &app.config)
1481    } else {
1482        Ok(())
1483    }
1484}
1485
1486fn save_required(app: &mut App) -> bool {
1487    let latest_save_file_info = get_latest_save_file(&app.config);
1488    if let Ok(save_file_name) = latest_save_file_info {
1489        let board_data = get_local_kanban_state(save_file_name, false, &app.config);
1490        match board_data {
1491            Ok(boards) => app.boards != boards,
1492            Err(_) => true,
1493        }
1494    } else {
1495        true
1496    }
1497}
1498
1499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1500pub struct CloudData {
1501    pub id: u64,
1502    pub created_at: String,
1503    pub user_id: String,
1504    pub board_data: String,
1505    pub nonce: String,
1506    pub save_id: usize,
1507}
1508
1509enum PasswordStatus {
1510    Strong,
1511    MissingUppercase,
1512    MissingLowercase,
1513    MissingNumber,
1514    MissingSpecialChar,
1515    TooShort,
1516    TooLong,
1517}
1518
1519fn check_for_safe_password(check_password: &str) -> PasswordStatus {
1520    let mut password_status = PasswordStatus::Strong;
1521    if check_password.len() < MIN_PASSWORD_LENGTH {
1522        password_status = PasswordStatus::TooShort;
1523    }
1524    if !check_password.chars().any(|c| c.is_uppercase()) {
1525        password_status = PasswordStatus::MissingUppercase;
1526    }
1527    if !check_password.chars().any(|c| c.is_lowercase()) {
1528        password_status = PasswordStatus::MissingLowercase;
1529    }
1530    if !check_password.chars().any(|c| c.is_numeric()) {
1531        password_status = PasswordStatus::MissingNumber;
1532    }
1533    if !check_password.chars().any(|c| c.is_ascii_punctuation()) {
1534        password_status = PasswordStatus::MissingSpecialChar;
1535    }
1536    if check_password.len() > MAX_PASSWORD_LENGTH {
1537        password_status = PasswordStatus::TooLong;
1538    }
1539    password_status
1540}
1541
1542fn encrypt_save(boards: &Boards, key: &[u8]) -> Result<(String, String), String> {
1543    let base64_engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
1544    let boards_json = serde_json::to_string(&boards);
1545    if boards_json.is_err() {
1546        return Err("Error serializing boards".to_string());
1547    }
1548    let boards_json = boards_json.unwrap();
1549    let key = Key::<Aes256Gcm>::from_slice(key);
1550    let cipher = Aes256Gcm::new(key);
1551    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
1552    let nonce_vec = nonce.to_vec();
1553    let nonce_encoded = base64_engine.encode(nonce_vec);
1554    let encrypted_boards = cipher.encrypt(&nonce, boards_json.as_bytes());
1555    if encrypted_boards.is_err() {
1556        return Err("Error encrypting boards".to_string());
1557    }
1558    let encrypted_boards = encrypted_boards.unwrap();
1559    let encoded_boards = base64_engine.encode(encrypted_boards);
1560    Ok((encoded_boards, nonce_encoded))
1561}
1562
1563fn decrypt_save(
1564    encrypted_boards: String,
1565    key: &[u8],
1566    encoded_nonce: &str,
1567) -> Result<Boards, String> {
1568    let base64_engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
1569    let encrypted_boards = base64_engine.decode(encrypted_boards);
1570    if encrypted_boards.is_err() {
1571        return Err("Error decoding boards".to_string());
1572    }
1573    let encrypted_boards = encrypted_boards.unwrap();
1574    let key = Key::<Aes256Gcm>::from_slice(key);
1575    let cipher = Aes256Gcm::new(key);
1576    let nonce = base64_engine.decode(encoded_nonce);
1577    if nonce.is_err() {
1578        return Err("Error decoding nonce".to_string());
1579    }
1580    let nonce = nonce.unwrap();
1581    let nonce = GenericArray::from_slice(&nonce);
1582    let decrypted_board_data = cipher.decrypt(nonce, encrypted_boards.as_slice());
1583    if decrypted_board_data.is_err() {
1584        return Err("Error decrypting boards".to_string());
1585    }
1586    let decrypted_board_data = decrypted_board_data.unwrap();
1587    let decrypted_board_data = String::from_utf8(decrypted_board_data);
1588    if decrypted_board_data.is_err() {
1589        return Err("Error converting decrypted boards to string".to_string());
1590    }
1591    let decrypted_board_data = decrypted_board_data.unwrap();
1592    let boards = serde_json::from_str(&decrypted_board_data);
1593    if boards.is_err() {
1594        return Err("Error deserializing boards".to_string());
1595    }
1596    Ok(boards.unwrap())
1597}
1598
1599pub fn save_user_encryption_key(key: &[u8]) -> Result<String> {
1600    let base64_engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
1601    let key = base64_engine.encode(key);
1602    let mut config_dir = get_config_dir().unwrap();
1603    config_dir.push(ENCRYPTION_KEY_FILE_NAME);
1604    let file_creation_status = std::fs::write(&config_dir, key);
1605    if let Err(e) = file_creation_status {
1606        Err(anyhow!(e))
1607    } else {
1608        Ok(config_dir.to_str().unwrap().to_string())
1609    }
1610}
1611
1612fn get_user_encryption_key(encryption_key_from_arguments: Option<String>) -> Result<Vec<u8>> {
1613    let base64_engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
1614    if let Some(encryption_key_from_arguments) = encryption_key_from_arguments {
1615        let decoded_key = base64_engine.decode(encryption_key_from_arguments);
1616        if let Err(e) = decoded_key {
1617            Err(anyhow!(e))
1618        } else {
1619            Ok(decoded_key.unwrap())
1620        }
1621    } else {
1622        let mut encryption_key_path = get_config_dir().unwrap();
1623        encryption_key_path.push(ENCRYPTION_KEY_FILE_NAME);
1624        if !encryption_key_path.exists() {
1625            return Err(anyhow!("Encryption key file not found!! Please generate a new one by using the -g flag or move it to the path: {:?}", encryption_key_path));
1626        }
1627        let encoded_key = std::fs::read_to_string(&encryption_key_path);
1628        if let Err(e) = encoded_key {
1629            Err(anyhow!(e))
1630        } else {
1631            let key = encoded_key.unwrap();
1632            let decoded_key = base64_engine.decode(key);
1633            if let Err(e) = decoded_key {
1634                Err(anyhow!(e))
1635            } else {
1636                Ok(decoded_key.unwrap())
1637            }
1638        }
1639    }
1640}
1641
1642pub fn generate_new_encryption_key() -> Vec<u8> {
1643    Aes256Gcm::generate_key(&mut OsRng).to_vec()
1644}
1645
1646pub async fn get_all_save_ids_for_user(user_id: String, access_token: &str) -> Result<Vec<usize>> {
1647    let client = reqwest::Client::new();
1648    let response = client
1649        .get(format!(
1650            "{}/rest/v1/user_data?user_id=eq.{}&select=save_id",
1651            SUPABASE_URL, user_id
1652        ))
1653        .header("apikey", SUPABASE_ANON_KEY)
1654        .header("Content-Type", "application/json")
1655        .header("Authorization", format!("Bearer {}", access_token))
1656        .header("Range", "0-9")
1657        .send()
1658        .await;
1659    if response.is_err() {
1660        debug!("Error getting save ids: {:?}", response.err());
1661        return Err(anyhow!("Error getting save ids".to_string()));
1662    }
1663    let response = response.unwrap();
1664    let status = response.status();
1665    if status == StatusCode::OK {
1666        let body = response.json::<serde_json::Value>().await;
1667        match body {
1668            Ok(save_instances) => {
1669                let mut save_ids: Vec<usize> = Vec::new();
1670                let save_instances_as_array = save_instances.as_array();
1671                if save_instances_as_array.is_none() {
1672                    return Err(anyhow!("Error getting save ids".to_string()));
1673                }
1674                let save_instances_as_array = save_instances_as_array.unwrap();
1675                for save_instance in save_instances_as_array {
1676                    let save_id = save_instance.get("save_id");
1677                    if save_id.is_none() {
1678                        return Err(anyhow!("Error getting save ids".to_string()));
1679                    }
1680                    let save_id = save_id.unwrap().as_u64();
1681                    if save_id.is_none() {
1682                        return Err(anyhow!("Error getting save ids".to_string()));
1683                    }
1684                    debug!("Found save_id: {:?}", save_id.unwrap() as usize);
1685                    save_ids.push(save_id.unwrap() as usize);
1686                }
1687                debug!("Save instances: {:?}", save_instances);
1688                Ok(save_ids)
1689            }
1690            Err(e) => {
1691                debug!("Error getting save ids: {:?}", e);
1692                Err(anyhow!("Error getting save ids".to_string()))
1693            }
1694        }
1695    } else {
1696        debug!("Status: {:?}", status);
1697        debug!("Error getting save ids: {:?}", response.text().await);
1698        Err(anyhow!("Error getting save ids".to_string()))
1699    }
1700}
1701
1702pub async fn get_all_save_ids_and_creation_dates_for_user(
1703    user_id: String,
1704    access_token: &str,
1705    cli_mode: bool,
1706) -> Result<Vec<(usize, String, usize)>> {
1707    let client = reqwest::Client::new();
1708    let response = client
1709        .get(format!(
1710            "{}/rest/v1/user_data?user_id=eq.{}&select=save_id,created_at,id",
1711            SUPABASE_URL, user_id
1712        ))
1713        .header("apikey", SUPABASE_ANON_KEY)
1714        .header("Content-Type", "application/json")
1715        .header("Authorization", format!("Bearer {}", access_token))
1716        .send()
1717        .await;
1718    if let Err(e) = response {
1719        debug!("Error getting save ids and created_at: {:?}", e);
1720        return Err(anyhow!("Error getting save ids".to_string()));
1721    }
1722    let response = response.unwrap();
1723    let status = response.status();
1724    if status == StatusCode::OK {
1725        let body = response.json::<serde_json::Value>().await;
1726        match body {
1727            Ok(save_instances) => {
1728                let mut save_ids_and_creation_dates: Vec<(usize, String, usize)> = Vec::new();
1729                let save_instances_as_array = save_instances.as_array();
1730                if save_instances_as_array.is_none() {
1731                    return Err(anyhow!("Error getting save ids".to_string()));
1732                }
1733                let save_instances_as_array = save_instances_as_array.unwrap().to_owned();
1734                for save_instance in save_instances_as_array {
1735                    let save_id = save_instance.get("save_id");
1736                    if save_id.is_none() {
1737                        return Err(anyhow!("Error getting save ids".to_string()));
1738                    }
1739                    let save_id = save_id.unwrap().as_u64();
1740                    if save_id.is_none() {
1741                        return Err(anyhow!("Error getting save ids".to_string()));
1742                    }
1743                    let save_id = save_id.unwrap() as usize;
1744                    let created_at = save_instance.get("created_at");
1745                    if created_at.is_none() {
1746                        return Err(anyhow!("Error getting save ids".to_string()));
1747                    }
1748                    let created_at = created_at.unwrap().as_str();
1749                    if created_at.is_none() {
1750                        return Err(anyhow!("Error getting save ids".to_string()));
1751                    }
1752                    let created_at = created_at.unwrap().to_owned();
1753                    let id = save_instance.get("id");
1754                    if id.is_none() {
1755                        return Err(anyhow!("Error getting save ids".to_string()));
1756                    }
1757                    let id = id.unwrap().as_u64();
1758                    if id.is_none() {
1759                        return Err(anyhow!("Error getting save ids".to_string()));
1760                    }
1761                    let id = id.unwrap() as usize;
1762                    if cli_mode {
1763                        print_debug(&format!("save_id: {:?}", save_id));
1764                        print_debug(&format!("created_at: {:?}", created_at));
1765                        print_debug(&format!("id: {:?}", id));
1766                    } else {
1767                        debug!("save_id: {:?}", save_id);
1768                        debug!("created_at: {:?}", created_at);
1769                        debug!("id: {:?}", id);
1770                    }
1771                    save_ids_and_creation_dates.push((save_id, created_at, id));
1772                }
1773                if cli_mode {
1774                    print_debug(&format!("save_instances: {:?}", save_instances));
1775                } else {
1776                    debug!("save_instances: {:?}", save_instances);
1777                }
1778                Ok(save_ids_and_creation_dates)
1779            }
1780            Err(e) => {
1781                if cli_mode {
1782                    print_debug(&format!("Error getting save ids: {:?}", e));
1783                } else {
1784                    debug!("Error getting save ids: {:?}", e);
1785                }
1786                Err(anyhow!("Error getting save ids".to_string()))
1787            }
1788        }
1789    } else {
1790        if cli_mode {
1791            print_debug(&format!("Status: {:?}", status));
1792            print_debug(&format!(
1793                "Error getting save ids: {:?}",
1794                response.text().await
1795            ));
1796        } else {
1797            debug!("Status: {:?}", status);
1798            debug!("Error getting save ids: {:?}", response.text().await);
1799        }
1800        Err(anyhow!("Error getting save ids".to_string()))
1801    }
1802}
1803
1804pub async fn delete_a_save_from_database(
1805    access_token: &str,
1806    cli_mode: bool,
1807    save_id: u64,
1808    save_number: Option<usize>,
1809) -> Result<String> {
1810    let client = reqwest::Client::new();
1811    let response = client
1812        .delete(format!(
1813            "{}/rest/v1/user_data?id=eq.{}",
1814            SUPABASE_URL, save_id
1815        ))
1816        .header("apikey", SUPABASE_ANON_KEY)
1817        .header("Content-Type", "application/json")
1818        .header("Authorization", format!("Bearer {}", access_token))
1819        .send()
1820        .await?;
1821    let status = response.status();
1822    if status == StatusCode::NO_CONTENT {
1823        if cli_mode {
1824            if save_number.is_some() {
1825                print_info(&format!("👍 Cloud save {} deleted", save_number.unwrap()));
1826            } else {
1827                print_info("👍 Cloud save deleted");
1828            }
1829            Ok("👍 Cloud save deleted".to_string())
1830        } else if save_number.is_some() {
1831            info!("👍 Cloud save {} deleted", save_number.unwrap());
1832            Ok(format!("👍 Cloud save {} deleted", save_number.unwrap()).to_string())
1833        } else {
1834            info!("👍 Cloud save deleted");
1835            Ok("👍 Cloud save deleted".to_string())
1836        }
1837    } else {
1838        let body = response.json::<serde_json::Value>().await;
1839        if cli_mode {
1840            print_error("Error deleting cloud save");
1841            print_debug(&format!(
1842                "status code {}, response body: {:?}",
1843                status, body
1844            ));
1845        } else {
1846            error!("Error deleting cloud save");
1847            debug!("status code {}, response body: {:?}", status, body);
1848        }
1849        Err(anyhow!("Error deleting cloud save"))
1850    }
1851}
1852
1853pub async fn get_user_id_from_database(access_token: &str, cli_mode: bool) -> Result<String> {
1854    let user_data_client = reqwest::Client::new();
1855    let user_data_response = user_data_client
1856        .get(format!("{}/auth/v1/user", SUPABASE_URL))
1857        .header("apikey", SUPABASE_ANON_KEY)
1858        .header("Content-Type", "application/json")
1859        .header("Authorization", format!("Bearer {}", access_token))
1860        .send()
1861        .await?;
1862    let user_data_status = user_data_response.status();
1863    let user_data_body = user_data_response.json::<serde_json::Value>().await;
1864    if user_data_status != StatusCode::OK {
1865        if cli_mode {
1866            print_error("Error retrieving user data");
1867            print_debug(&format!(
1868                "status code {}, response body: {:?}",
1869                user_data_status, user_data_body
1870            ));
1871        } else {
1872            error!("Error retrieving user data");
1873            debug!(
1874                "status code {}, response body: {:?}",
1875                user_data_status, user_data_body
1876            );
1877        }
1878        return Err(anyhow!("Error retrieving user data"));
1879    }
1880    let user_data_body = user_data_body.unwrap();
1881    let user_id = user_data_body.get("id");
1882    if user_id.is_none() {
1883        if cli_mode {
1884            print_error("Error retrieving user data");
1885            print_debug(&format!(
1886                "status code {}, response body: {:?}, could not find id",
1887                user_data_status, user_data_body
1888            ));
1889        } else {
1890            error!("Error retrieving user data");
1891            debug!(
1892                "status code {}, response body: {:?}, could not find id",
1893                user_data_status, user_data_body
1894            );
1895        }
1896        return Err(anyhow!("Error retrieving user data"));
1897    }
1898    let user_id = user_id.unwrap().as_str();
1899    if cli_mode {
1900        print_debug(&format!("user_id: {:?}", user_id));
1901    } else {
1902        debug!("user_id: {:?}", user_id);
1903    }
1904    Ok(user_id.unwrap().to_string())
1905}
1906
1907pub async fn login_for_user(
1908    email_id: &str,
1909    password: &str,
1910    cli_mode: bool,
1911) -> Result<(String, String, String), String> {
1912    let request_body = json!(
1913        {
1914            "email": email_id,
1915            "password": password
1916        }
1917    );
1918    let client = reqwest::Client::new();
1919    let response = client
1920        .post(format!(
1921            "{}/auth/v1/token?grant_type=password",
1922            SUPABASE_URL
1923        ))
1924        .header("apikey", SUPABASE_ANON_KEY)
1925        .header("Content-Type", "application/json")
1926        .body(request_body.to_string())
1927        .send()
1928        .await;
1929    if let Err(e) = response {
1930        if cli_mode {
1931            print_debug(&format!("Error logging in: {}", e));
1932            print_error("Error logging in, Something went wrong, please try again later");
1933        } else {
1934            debug!("Error logging in: {}", e);
1935            error!("Error logging in, Something went wrong, please try again later");
1936        }
1937        return Err("Error logging in, Something went wrong, please try again later".to_string());
1938    }
1939    let response = response.unwrap();
1940    let status = response.status();
1941    let body = response.json::<serde_json::Value>().await;
1942    if status == StatusCode::OK {
1943        match body {
1944            Ok(body) => {
1945                let access_token = body.get("access_token");
1946                let refresh_token = body.get("refresh_token");
1947                let access_token_result = match access_token {
1948                    Some(access_token) => {
1949                        let access_token = access_token.as_str().unwrap();
1950                        if cli_mode {
1951                            print_info("🚀 Login successful");
1952                            print_debug(&format!("Access token: {}", access_token));
1953                        } else {
1954                            info!("🚀 Login successful");
1955                            debug!("Access token: {}", access_token);
1956                        }
1957                        let user_id = get_user_id_from_database(access_token, cli_mode)
1958                            .await
1959                            .unwrap_or_else(|_| "Error getting user id".to_string());
1960                        Ok((access_token.to_string(), user_id))
1961                    }
1962                    None => {
1963                        if cli_mode {
1964                            print_error("Error logging in");
1965                            print_debug(&format!(
1966                                "status code {}, response body: {:?}, could not find access token",
1967                                status, body
1968                            ));
1969                        } else {
1970                            error!("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer");
1971                            debug!(
1972                                "status code {}, response body: {:?}, could not find access token",
1973                                status, body
1974                            );
1975                        }
1976                        Err("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer".to_string())
1977                    }
1978                };
1979                let refresh_token_result = match refresh_token {
1980                    Some(refresh_token) => {
1981                        let refresh_token = refresh_token.as_str().unwrap();
1982                        if cli_mode {
1983                            print_debug(&format!("Refresh token: {}", refresh_token));
1984                        } else {
1985                            debug!("Refresh token: {}", refresh_token);
1986                        }
1987                        Ok(refresh_token.to_string())
1988                    }
1989                    None => {
1990                        if cli_mode {
1991                            print_error("Error logging in");
1992                            print_debug(&format!(
1993                                "status code {}, response body: {:?}, could not find refresh token",
1994                                status, body
1995                            ));
1996                        } else {
1997                            error!("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer");
1998                            debug!(
1999                                "status code {}, response body: {:?}, could not find refresh token",
2000                                status, body
2001                            );
2002                        }
2003                        Err("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer".to_string())
2004                    }
2005                };
2006
2007                if access_token_result.is_err() || refresh_token_result.is_err() {
2008                    Err("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer".to_string())
2009                } else {
2010                    let access_token_result = access_token_result.unwrap();
2011                    let refresh_token_result = refresh_token_result.unwrap();
2012                    Ok((
2013                        access_token_result.0,
2014                        access_token_result.1,
2015                        refresh_token_result,
2016                    ))
2017                }
2018            }
2019            Err(e) => Err(format!("Error logging in: {}", e)),
2020        }
2021    } else if status == StatusCode::TOO_MANY_REQUESTS {
2022        if cli_mode {
2023            print_error("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢");
2024            print_debug(&format!(
2025                "status code {}, response body: {:?}",
2026                status, body
2027            ));
2028        } else {
2029            error!("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢");
2030            debug!("status code {}, response body: {:?}", status, body);
2031        }
2032        Err("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢".to_string())
2033    } else {
2034        match body {
2035            Ok(body) => {
2036                let error_description = body.get("error_description");
2037                match error_description {
2038                    Some(error_description) => {
2039                        let error_description = error_description.to_string();
2040                        if cli_mode {
2041                            print_error(&error_description);
2042                            print_debug(&format!(
2043                                "status code {}, response body: {:?}",
2044                                status, body
2045                            ));
2046                        } else {
2047                            error!("{}", error_description);
2048                            debug!("status code {}, response body: {:?}", status, body);
2049                        }
2050                        Err(format!("Error logging in: {}", error_description))
2051                    }
2052                    None => {
2053                        if cli_mode {
2054                            print_error("Error logging in");
2055                            print_debug(&format!(
2056                                "status code {}, response body: {:?}",
2057                                status, body
2058                            ));
2059                        } else {
2060                            error!("Error logging in");
2061                            debug!("status code {}, response body: {:?}", status, body);
2062                        }
2063                        Err("Error logging in".to_string())
2064                    }
2065                }
2066            }
2067            Err(e) => {
2068                if cli_mode {
2069                    print_error(&format!("Error logging in: {}", e));
2070                } else {
2071                    error!("Error logging in: {}", e);
2072                }
2073                Err(format!("Error logging in: {}", e))
2074            }
2075        }
2076    }
2077}
2078
2079async fn save_refresh_token_to_disk(
2080    refresh_token: &str,
2081    email_id: &str,
2082    encryption_key_from_arguments: Option<String>,
2083) -> Result<()> {
2084    let base64_engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
2085    let refresh_token_path = get_config_dir();
2086    if refresh_token_path.is_err() {
2087        return Err(anyhow!("Error getting config directory"));
2088    }
2089    let mut refresh_token_path = refresh_token_path.unwrap();
2090    refresh_token_path.push(REFRESH_TOKEN_FILE_NAME);
2091    if refresh_token_path.exists() {
2092        let delete_file_status = std::fs::remove_file(&refresh_token_path);
2093        if delete_file_status.is_err() {
2094            return Err(anyhow!("Error deleting refresh token file"));
2095        }
2096    }
2097    let encryption_key = get_user_encryption_key(encryption_key_from_arguments);
2098    if let Err(e) = encryption_key {
2099        return Err(anyhow!(e));
2100    }
2101    let encryption_key = encryption_key.unwrap();
2102    let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
2103    let cipher = Aes256Gcm::new(key);
2104    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
2105    let encrypted_refresh_token = cipher.encrypt(&nonce, refresh_token.as_bytes());
2106    if encrypted_refresh_token.is_err() {
2107        return Err(anyhow!("Error encrypting refresh token"));
2108    }
2109    let encrypted_refresh_token = encrypted_refresh_token.unwrap();
2110    let nonce = nonce.to_vec();
2111    let nonce = base64_engine.encode(nonce);
2112    let encrypted_refresh_token = base64_engine.encode(encrypted_refresh_token);
2113    let encoded_email_id = base64_engine.encode(email_id.as_bytes());
2114    let refresh_token_data = format!(
2115        "{}{}{}{}{}",
2116        nonce,
2117        REFRESH_TOKEN_SEPARATOR,
2118        encrypted_refresh_token,
2119        REFRESH_TOKEN_SEPARATOR,
2120        encoded_email_id
2121    );
2122    let file_creation_status = std::fs::write(&refresh_token_path, refresh_token_data);
2123    if file_creation_status.is_err() {
2124        return Err(anyhow!("Error creating refresh token file"));
2125    }
2126    Ok(())
2127}
2128
2129fn get_refresh_token_from_disk(
2130    encryption_key_from_arguments: Option<String>,
2131) -> Result<(String, String)> {
2132    let base64_engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
2133    let refresh_token_path = get_config_dir();
2134    if refresh_token_path.is_err() {
2135        return Err(anyhow!("Error getting config directory"));
2136    }
2137    let mut refresh_token_path = refresh_token_path.unwrap();
2138    refresh_token_path.push(REFRESH_TOKEN_FILE_NAME);
2139    let refresh_token_data = std::fs::read_to_string(&refresh_token_path);
2140    if refresh_token_data.is_err() {
2141        return Err(anyhow!("Error reading refresh token file"));
2142    }
2143    let refresh_token_data = refresh_token_data.unwrap();
2144    let refresh_token_data = refresh_token_data
2145        .split(REFRESH_TOKEN_SEPARATOR)
2146        .collect::<Vec<&str>>();
2147    if refresh_token_data.len() != 3 {
2148        return Err(anyhow!("Error reading refresh token file"));
2149    }
2150    let nonce = refresh_token_data[0];
2151    let nonce = base64_engine.decode(nonce);
2152    if nonce.is_err() {
2153        return Err(anyhow!("Error reading refresh token file"));
2154    }
2155    let nonce = nonce.unwrap();
2156    let nonce = GenericArray::from_slice(&nonce);
2157    let encrypted_refresh_token = refresh_token_data[1];
2158    let encrypted_refresh_token = base64_engine.decode(encrypted_refresh_token);
2159    if encrypted_refresh_token.is_err() {
2160        return Err(anyhow!("Error reading refresh token file"));
2161    }
2162    let encrypted_refresh_token = encrypted_refresh_token.unwrap();
2163    let email_id = refresh_token_data[2];
2164    let email_id = base64_engine.decode(email_id);
2165    if email_id.is_err() {
2166        return Err(anyhow!("Error reading refresh token file"));
2167    }
2168    let email_id = email_id.unwrap();
2169    let email_id = String::from_utf8(email_id);
2170    if email_id.is_err() {
2171        return Err(anyhow!("Error reading refresh token file"));
2172    }
2173    let email_id = email_id.unwrap();
2174    let encryption_key = get_user_encryption_key(encryption_key_from_arguments);
2175    if let Err(e) = encryption_key {
2176        return Err(anyhow!(e));
2177    }
2178    let encryption_key = encryption_key.unwrap();
2179    let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
2180    let cipher = Aes256Gcm::new(key);
2181    let decrypted_refresh_token = cipher.decrypt(nonce, encrypted_refresh_token.as_slice());
2182    if decrypted_refresh_token.is_err() {
2183        return Err(anyhow!("Error decrypting refresh token"));
2184    }
2185    let decrypted_refresh_token = decrypted_refresh_token.unwrap();
2186    let decrypted_refresh_token = String::from_utf8(decrypted_refresh_token);
2187    if decrypted_refresh_token.is_err() {
2188        return Err(anyhow!(
2189            "Error converting decrypted refresh token to string"
2190        ));
2191    }
2192    let decrypted_refresh_token = decrypted_refresh_token.unwrap();
2193    Ok((decrypted_refresh_token, email_id))
2194}
2195
2196async fn delete_refresh_token_from_disk() -> Result<()> {
2197    let refresh_token_path = get_config_dir();
2198    if refresh_token_path.is_err() {
2199        return Err(anyhow!("Error getting config directory"));
2200    }
2201    let mut refresh_token_path = refresh_token_path.unwrap();
2202    refresh_token_path.push(REFRESH_TOKEN_FILE_NAME);
2203    if !refresh_token_path.exists() {
2204        return Ok(());
2205    }
2206    let delete_file_status = std::fs::remove_file(&refresh_token_path);
2207    if delete_file_status.is_err() {
2208        return Err(anyhow!("Error deleting refresh token file"));
2209    }
2210    Ok(())
2211}
2212
2213async fn refresh_access_token(refresh_token: &str) -> Result<(String, String, String), String> {
2214    let request_body = json!(
2215        {
2216            "grant_type": "refresh_token",
2217            "refresh_token": refresh_token
2218        }
2219    );
2220    let client = reqwest::Client::new();
2221    let response = client
2222        .post(format!(
2223            "{}/auth/v1/token?grant_type=refresh_token",
2224            SUPABASE_URL
2225        ))
2226        .header("apikey", SUPABASE_ANON_KEY)
2227        .header("Content-Type", "application/json")
2228        .body(request_body.to_string())
2229        .send()
2230        .await;
2231    if let Err(e) = response {
2232        debug!("Error logging in: {}", e);
2233        error!("Error logging in, Something went wrong, please try again later");
2234        return Err("Error logging in, Something went wrong, please try again later".to_string());
2235    }
2236    let response = response.unwrap();
2237    let status = response.status();
2238    let body = response.json::<serde_json::Value>().await;
2239    if status == StatusCode::OK {
2240        match body {
2241            Ok(body) => {
2242                let access_token = body.get("access_token");
2243                let refresh_token = body.get("refresh_token");
2244                let access_token_result = match access_token {
2245                    Some(access_token) => {
2246                        let access_token = access_token.as_str().unwrap();
2247                        info!("🚀 Login successful");
2248                        debug!("Access token: {}", access_token);
2249                        let user_id = get_user_id_from_database(access_token, false)
2250                            .await
2251                            .unwrap_or_else(|_| "Error getting user id".to_string());
2252                        Ok((access_token.to_string(), user_id))
2253                    }
2254                    None => {
2255                        error!("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer");
2256                        debug!(
2257                            "status code {}, response body: {:?}, could not find access token",
2258                            status, body
2259                        );
2260                        Err("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer".to_string())
2261                    }
2262                };
2263                let refresh_token_result = match refresh_token {
2264                    Some(refresh_token) => {
2265                        let refresh_token = refresh_token.as_str().unwrap();
2266                        debug!("Refresh token: {}", refresh_token);
2267                        Ok(refresh_token.to_string())
2268                    }
2269                    None => {
2270                        error!("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer");
2271                        debug!(
2272                            "status code {}, response body: {:?}, could not find refresh token",
2273                            status, body
2274                        );
2275                        Err("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer".to_string())
2276                    }
2277                };
2278
2279                if access_token_result.is_err() || refresh_token_result.is_err() {
2280                    Err("Error logging in, If this is your first login attempt after signup please login again, if it is not please contact the developer".to_string())
2281                } else {
2282                    let access_token_result = access_token_result.unwrap();
2283                    let refresh_token_result = refresh_token_result.unwrap();
2284                    Ok((
2285                        access_token_result.0,
2286                        access_token_result.1,
2287                        refresh_token_result,
2288                    ))
2289                }
2290            }
2291            Err(e) => Err(format!("Error logging in: {}", e)),
2292        }
2293    } else if status == StatusCode::TOO_MANY_REQUESTS {
2294        error!("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢");
2295        debug!("status code {}, response body: {:?}", status, body);
2296        Err("Too many requests, please try again later. Due to the free nature of supabase i am limited to only 4 signup requests per hour. Sorry! 😢".to_string())
2297    } else {
2298        match body {
2299            Ok(body) => {
2300                let error_description = body.get("error_description");
2301                match error_description {
2302                    Some(error_description) => {
2303                        let error_description = error_description.to_string();
2304                        error!("{}", error_description);
2305                        debug!("status code {}, response body: {:?}", status, body);
2306                        Err(format!("Error logging in: {}", error_description))
2307                    }
2308                    None => {
2309                        error!("Error logging in");
2310                        debug!("status code {}, response body: {:?}", status, body);
2311                        Err("Error logging in".to_string())
2312                    }
2313                }
2314            }
2315            Err(e) => {
2316                error!("Error logging in: {}", e);
2317                Err(format!("Error logging in: {}", e))
2318            }
2319        }
2320    }
2321}
2322
2323async fn test_refresh_token_on_disk(
2324    encryption_key_from_arguments: Option<String>,
2325) -> Result<UserLoginData> {
2326    let (refresh_token, email_id) =
2327        get_refresh_token_from_disk(encryption_key_from_arguments.clone())?;
2328    debug!("refresh_token: {:?}", refresh_token);
2329    let status = refresh_access_token(&refresh_token).await;
2330    if status.is_err() {
2331        return Err(anyhow!(status.err().unwrap()));
2332    }
2333    let status = status.unwrap();
2334    let access_token = status.0;
2335    let user_id = status.1;
2336    let refresh_token = status.2;
2337    let save_status =
2338        save_refresh_token_to_disk(&refresh_token, &email_id, encryption_key_from_arguments).await;
2339    if save_status.is_err() {
2340        error!("Error saving refresh token to disk");
2341    }
2342    let user_data = UserLoginData {
2343        auth_token: Some(access_token),
2344        email_id: Some(email_id),
2345        refresh_token: Some(refresh_token),
2346        user_id: Some(user_id),
2347    };
2348    Ok(user_data)
2349}