2024-05-12 12:34:29 +00:00
// Prevents an additional console window on Windows in release, DO NOT REMOVE!!
2024-03-28 21:26:48 +00:00
#![ cfg_attr(not(debug_assertions), windows_subsystem = " windows " ) ]
2024-09-01 18:10:03 +00:00
extern crate rocket ;
2024-05-12 12:40:06 +00:00
extern crate core ;
2024-11-05 14:14:17 +00:00
use std ::collections ::{ HashMap , HashSet } ;
2024-05-12 12:40:06 +00:00
use std ::net ::TcpListener ;
2024-09-01 18:10:03 +00:00
use std ::sync ::{ Arc , Mutex , OnceLock } ;
2024-11-04 19:42:12 +00:00
use std ::time ::Duration ;
2024-06-30 13:26:28 +00:00
use once_cell ::sync ::Lazy ;
2024-05-12 12:40:06 +00:00
2024-05-03 20:55:21 +00:00
use arboard ::Clipboard ;
2024-09-01 18:10:03 +00:00
use base64 ::Engine ;
use base64 ::prelude ::BASE64_STANDARD ;
2024-04-05 20:23:01 +00:00
use keyring ::Entry ;
2024-09-01 18:10:03 +00:00
use serde ::{ Deserialize , Serialize } ;
use tauri ::{ Manager , Url , Window } ;
2024-05-12 12:40:06 +00:00
use tauri ::api ::process ::{ Command , CommandChild , CommandEvent } ;
use tokio ::time ;
2024-07-28 14:55:38 +00:00
use keyring ::error ::Error ::NoEntry ;
2024-11-05 14:14:17 +00:00
use log ::{ debug , error , info , warn } ;
2024-09-01 18:10:03 +00:00
use rand ::{ RngCore , SeedableRng } ;
use rcgen ::generate_simple_self_signed ;
use rocket ::figment ::Figment ;
2024-11-04 19:42:12 +00:00
use rocket ::{ get , post , routes , Request } ;
2024-09-01 18:10:03 +00:00
use rocket ::config ::{ Shutdown } ;
use rocket ::http ::Status ;
use rocket ::request ::{ FromRequest } ;
use rocket ::serde ::json ::Json ;
2024-11-04 19:42:12 +00:00
use sha2 ::{ Sha256 , Digest } ;
2024-06-30 13:26:28 +00:00
use tauri ::updater ::UpdateResponse ;
2024-11-04 19:42:12 +00:00
use mindwork_ai_studio ::encryption ::{ EncryptedText , ENCRYPTION } ;
2024-11-05 14:14:17 +00:00
use mindwork_ai_studio ::environment ::{ is_dev , is_prod } ;
use mindwork_ai_studio ::log ::{ init_logging , switch_to_file_logging } ;
2024-09-01 18:10:03 +00:00
// The port used for the runtime API server. In the development environment, we use a fixed
// port, in the production environment we use the next available port. This differentiation
// is necessary because we cannot communicate the port to the .NET server in the development
// environment.
static API_SERVER_PORT : Lazy < u16 > = Lazy ::new ( | | {
if is_dev ( ) {
5000
} else {
get_available_port ( ) . unwrap ( )
}
} ) ;
// The Tauri main window.
2024-06-30 13:26:28 +00:00
static MAIN_WINDOW : Lazy < Mutex < Option < Window > > > = Lazy ::new ( | | Mutex ::new ( None ) ) ;
2024-09-01 18:10:03 +00:00
// The update response coming from the Tauri updater.
2024-06-30 13:26:28 +00:00
static CHECK_UPDATE_RESPONSE : Lazy < Mutex < Option < UpdateResponse < tauri ::Wry > > > > = Lazy ::new ( | | Mutex ::new ( None ) ) ;
2024-04-05 20:23:01 +00:00
2024-09-01 18:10:03 +00:00
#[ tokio::main ]
async fn main ( ) {
2024-05-12 12:40:06 +00:00
2024-05-21 16:55:35 +00:00
let metadata = include_str! ( " ../../metadata.txt " ) ;
let mut metadata_lines = metadata . lines ( ) ;
let app_version = metadata_lines . next ( ) . unwrap ( ) ;
let build_time = metadata_lines . next ( ) . unwrap ( ) ;
let build_number = metadata_lines . next ( ) . unwrap ( ) ;
let dotnet_sdk_version = metadata_lines . next ( ) . unwrap ( ) ;
let dotnet_version = metadata_lines . next ( ) . unwrap ( ) ;
let rust_version = metadata_lines . next ( ) . unwrap ( ) ;
let mud_blazor_version = metadata_lines . next ( ) . unwrap ( ) ;
let tauri_version = metadata_lines . next ( ) . unwrap ( ) ;
let app_commit_hash = metadata_lines . next ( ) . unwrap ( ) ;
2024-11-05 14:14:17 +00:00
init_logging ( ) ;
2024-05-12 12:40:06 +00:00
2024-05-21 16:55:35 +00:00
info! ( " Starting MindWork AI Studio: " ) ;
2024-09-03 14:13:57 +00:00
let working_directory = std ::env ::current_dir ( ) . unwrap ( ) ;
info! ( " .. The working directory is: '{working_directory:?}' " ) ;
2024-05-21 16:55:35 +00:00
info! ( " .. Version: v{app_version} (commit {app_commit_hash}, build {build_number}) " ) ;
info! ( " .. Build time: {build_time} " ) ;
info! ( " .. .NET SDK: v{dotnet_sdk_version} " ) ;
info! ( " .. .NET: v{dotnet_version} " ) ;
info! ( " .. Rust: v{rust_version} " ) ;
info! ( " .. MudBlazor: v{mud_blazor_version} " ) ;
info! ( " .. Tauri: v{tauri_version} " ) ;
2024-05-12 12:40:06 +00:00
if is_dev ( ) {
warn! ( " Running in development mode. " ) ;
} else {
info! ( " Running in production mode. " ) ;
}
2024-09-01 18:10:03 +00:00
info! ( " Try to generate a TLS certificate for the runtime API server... " ) ;
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
let subject_alt_names = vec! [ " localhost " . to_string ( ) ] ;
let certificate_data = generate_simple_self_signed ( subject_alt_names ) . unwrap ( ) ;
let certificate_binary_data = certificate_data . cert . der ( ) . to_vec ( ) ;
let certificate_fingerprint = Sha256 ::digest ( certificate_binary_data ) . to_vec ( ) ;
let certificate_fingerprint = certificate_fingerprint . iter ( ) . fold ( String ::new ( ) , | mut result , byte | {
result . push_str ( & format! ( " {:02x} " , byte ) ) ;
result
} ) ;
let certificate_fingerprint = certificate_fingerprint . to_uppercase ( ) ;
info! ( " Certificate fingerprint: '{certificate_fingerprint}'. " ) ;
info! ( " Done generating certificate for the runtime API server. " ) ;
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
let api_port = * API_SERVER_PORT ;
info! ( " Try to start the API server on 'http://localhost:{api_port}'... " ) ;
2024-05-12 12:40:06 +00:00
2024-09-01 19:54:23 +00:00
// The shutdown configuration for the runtime API server:
let mut shutdown = Shutdown {
// We do not want to use the Ctrl+C signal to stop the server:
ctrlc : false ,
2024-09-03 14:13:57 +00:00
2024-09-01 19:54:23 +00:00
// Everything else is set to default for now:
.. Shutdown ::default ( )
} ;
#[ cfg(unix) ]
{
// We do not want to use the termination signal to stop the server.
// This option, however, is only available on Unix systems:
shutdown . signals = HashSet ::new ( ) ;
}
2024-09-01 18:10:03 +00:00
// Configure the runtime API server:
let figment = Figment ::from ( rocket ::Config ::release_default ( ) )
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
// We use the next available port which was determined before:
. merge ( ( " port " , api_port ) )
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
// The runtime API server should be accessible only from the local machine:
. merge ( ( " address " , " 127.0.0.1 " ) )
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
// We do not want to use the Ctrl+C signal to stop the server:
. merge ( ( " ctrlc " , false ) )
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
// Set a name for the server:
. merge ( ( " ident " , " AI Studio Runtime API " ) )
// Set the maximum number of workers and blocking threads:
. merge ( ( " workers " , 3 ) )
. merge ( ( " max_blocking " , 12 ) )
// No colors and emojis in the log output:
. merge ( ( " cli_colors " , false ) )
// Read the TLS certificate and key from the generated certificate data in-memory:
. merge ( ( " tls.certs " , certificate_data . cert . pem ( ) . as_bytes ( ) ) )
. merge ( ( " tls.key " , certificate_data . key_pair . serialize_pem ( ) . as_bytes ( ) ) )
// Set the shutdown configuration:
2024-09-01 19:54:23 +00:00
. merge ( ( " shutdown " , shutdown ) ) ;
2024-09-01 18:10:03 +00:00
//
// Start the runtime API server in a separate thread. This is necessary
// because the server is blocking, and we need to run the Tauri app in
// parallel:
//
2024-05-12 12:40:06 +00:00
tauri ::async_runtime ::spawn ( async move {
2024-10-18 08:40:19 +00:00
rocket ::custom ( figment )
2024-09-01 18:10:03 +00:00
. mount ( " / " , routes! [
dotnet_port , dotnet_ready , set_clipboard , check_for_update , install_update ,
get_secret , store_secret , delete_secret , get_data_directory , get_config_directory ,
] )
. ignite ( ) . await . unwrap ( )
. launch ( ) . await . unwrap ( ) ;
} ) ;
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
info! ( " Secret password for the IPC channel was generated successfully. " ) ;
2024-05-12 12:40:06 +00:00
info! ( " Starting Tauri app... " ) ;
2024-06-30 13:26:28 +00:00
let app = tauri ::Builder ::default ( )
2024-05-12 12:40:06 +00:00
. setup ( move | app | {
let window = app . get_window ( " main " ) . expect ( " Failed to get main window. " ) ;
2024-06-30 13:26:28 +00:00
* MAIN_WINDOW . lock ( ) . unwrap ( ) = Some ( window ) ;
2024-09-01 18:10:03 +00:00
info! ( Source = " Bootloader Tauri " ; " Setup is running. " ) ;
let logger_path = app . path_resolver ( ) . app_local_data_dir ( ) . unwrap ( ) ;
let logger_path = logger_path . join ( " data " ) ;
DATA_DIRECTORY . set ( logger_path . to_str ( ) . unwrap ( ) . to_string ( ) ) . map_err ( | _ | error! ( " Was not abe to set the data directory. " ) ) . unwrap ( ) ;
CONFIG_DIRECTORY . set ( app . path_resolver ( ) . app_config_dir ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) . map_err ( | _ | error! ( " Was not able to set the config directory. " ) ) . unwrap ( ) ;
info! ( Source = " Bootloader Tauri " ; " Reconfigure the file logger to use the app data directory {logger_path:?} " ) ;
2024-11-05 14:14:17 +00:00
switch_to_file_logging ( logger_path ) . map_err ( | e | error! ( " Failed to switch logging to file: {e} " ) ) . unwrap ( ) ;
2024-09-01 18:10:03 +00:00
2024-05-12 12:40:06 +00:00
Ok ( ( ) )
} )
2024-06-01 10:49:10 +00:00
. plugin ( tauri_plugin_window_state ::Builder ::default ( ) . build ( ) )
2024-06-30 13:26:28 +00:00
. build ( tauri ::generate_context! ( ) )
2024-05-12 12:40:06 +00:00
. expect ( " Error while running Tauri application " ) ;
2024-06-30 13:26:28 +00:00
app . run ( | app_handle , event | match event {
tauri ::RunEvent ::WindowEvent { event , label , .. } = > {
match event {
tauri ::WindowEvent ::CloseRequested { .. } = > {
2024-09-01 18:10:03 +00:00
warn! ( Source = " Tauri " ; " Window '{label}': close was requested. " ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::WindowEvent ::Destroyed = > {
2024-09-01 18:10:03 +00:00
warn! ( Source = " Tauri " ; " Window '{label}': was destroyed. " ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::WindowEvent ::FileDrop ( files ) = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Window '{label}': files were dropped: {files:?} " ) ;
2024-06-30 13:26:28 +00:00
}
_ = > ( ) ,
}
}
tauri ::RunEvent ::Updater ( updater_event ) = > {
match updater_event {
tauri ::UpdaterEvent ::UpdateAvailable { body , date , version } = > {
let body_len = body . len ( ) ;
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Updater: update available: body size={body_len} time={date:?} version={version} " ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::UpdaterEvent ::Pending = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Updater: update is pending! " ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::UpdaterEvent ::DownloadProgress { chunk_length , content_length } = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Updater: downloaded {} of {:?} " , chunk_length , content_length ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::UpdaterEvent ::Downloaded = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Updater: update has been downloaded! " ) ;
warn! ( Source = " Tauri " ; " Try to stop the .NET server now... " ) ;
stop_servers ( ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::UpdaterEvent ::Updated = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Updater: app has been updated " ) ;
warn! ( Source = " Tauri " ; " Try to restart the app now... " ) ;
2024-06-30 13:26:28 +00:00
app_handle . restart ( ) ;
}
tauri ::UpdaterEvent ::AlreadyUpToDate = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Updater: app is already up to date " ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::UpdaterEvent ::Error ( error ) = > {
2024-09-01 18:10:03 +00:00
warn! ( Source = " Tauri " ; " Updater: failed to update: {error} " ) ;
2024-06-30 13:26:28 +00:00
}
}
}
tauri ::RunEvent ::ExitRequested { .. } = > {
2024-09-01 18:10:03 +00:00
warn! ( Source = " Tauri " ; " Run event: exit was requested. " ) ;
2024-06-30 13:26:28 +00:00
}
tauri ::RunEvent ::Ready = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Tauri " ; " Run event: Tauri app is ready. " ) ;
2024-06-30 13:26:28 +00:00
}
_ = > { }
} ) ;
2024-05-12 12:40:06 +00:00
2024-09-01 18:10:03 +00:00
warn! ( Source = " Tauri " ; " Tauri app was stopped. " ) ;
2024-05-12 12:40:06 +00:00
if is_prod ( ) {
2024-09-01 18:10:03 +00:00
warn! ( " Try to stop the .NET server as well... " ) ;
stop_servers ( ) ;
}
}
}
2024-06-30 13:26:28 +00:00
2024-09-01 18:10:03 +00:00
#[ get( " /updates/check " ) ]
async fn check_for_update ( _token : APIToken ) -> Json < CheckUpdateResponse > {
2024-06-30 13:26:28 +00:00
let app_handle = MAIN_WINDOW . lock ( ) . unwrap ( ) . as_ref ( ) . unwrap ( ) . app_handle ( ) ;
2024-09-01 18:10:03 +00:00
let response = app_handle . updater ( ) . check ( ) . await ;
match response {
Ok ( update_response ) = > match update_response . is_update_available ( ) {
true = > {
* CHECK_UPDATE_RESPONSE . lock ( ) . unwrap ( ) = Some ( update_response . clone ( ) ) ;
let new_version = update_response . latest_version ( ) ;
info! ( Source = " Updater " ; " An update to version '{new_version}' is available. " ) ;
let changelog = update_response . body ( ) ;
Json ( CheckUpdateResponse {
update_is_available : true ,
error : false ,
new_version : new_version . to_string ( ) ,
changelog : match changelog {
Some ( c ) = > c . to_string ( ) ,
None = > String ::from ( " " ) ,
} ,
} )
2024-06-30 13:26:28 +00:00
} ,
2024-09-01 18:10:03 +00:00
false = > {
info! ( Source = " Updater " ; " No updates are available. " ) ;
Json ( CheckUpdateResponse {
2024-06-30 13:26:28 +00:00
update_is_available : false ,
2024-09-01 18:10:03 +00:00
error : false ,
2024-06-30 13:26:28 +00:00
new_version : String ::from ( " " ) ,
changelog : String ::from ( " " ) ,
2024-09-01 18:10:03 +00:00
} )
2024-06-30 13:26:28 +00:00
} ,
2024-09-01 18:10:03 +00:00
} ,
Err ( e ) = > {
warn! ( Source = " Updater " ; " Failed to check for updates: {e}. " ) ;
Json ( CheckUpdateResponse {
update_is_available : false ,
error : true ,
new_version : String ::from ( " " ) ,
changelog : String ::from ( " " ) ,
} )
} ,
}
2024-06-30 13:26:28 +00:00
}
#[ derive(Serialize) ]
struct CheckUpdateResponse {
update_is_available : bool ,
error : bool ,
new_version : String ,
changelog : String ,
}
2024-09-01 18:10:03 +00:00
#[ get( " /updates/install " ) ]
async fn install_update ( _token : APIToken ) {
2024-06-30 13:26:28 +00:00
let cloned_response_option = CHECK_UPDATE_RESPONSE . lock ( ) . unwrap ( ) . clone ( ) ;
match cloned_response_option {
Some ( update_response ) = > {
update_response . download_and_install ( ) . await . unwrap ( ) ;
} ,
None = > {
2024-09-01 18:10:03 +00:00
error! ( Source = " Updater " ; " No update available to install. Did you check for updates first? " ) ;
2024-06-30 13:26:28 +00:00
} ,
}
}
2024-09-01 18:10:03 +00:00
#[ post( " /secrets/store " , data = " <request> " ) ]
fn store_secret ( _token : APIToken , request : Json < StoreSecret > ) -> Json < StoreSecretResponse > {
let user_name = request . user_name . as_str ( ) ;
let decrypted_text = match ENCRYPTION . decrypt ( & request . secret ) {
Ok ( text ) = > text ,
Err ( e ) = > {
error! ( Source = " Secret Store " ; " Failed to decrypt the text: {e}. " ) ;
return Json ( StoreSecretResponse {
success : false ,
issue : format ! ( " Failed to decrypt the text: {e} " ) ,
} )
} ,
} ;
let service = format! ( " mindwork-ai-studio:: {} " , request . destination ) ;
let entry = Entry ::new ( service . as_str ( ) , user_name ) . unwrap ( ) ;
let result = entry . set_password ( decrypted_text . as_str ( ) ) ;
2024-04-20 15:03:39 +00:00
match result {
2024-05-12 12:40:06 +00:00
Ok ( _ ) = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Secret Store " ; " Secret for {service} and user {user_name} was stored successfully. " ) ;
Json ( StoreSecretResponse {
2024-05-12 12:40:06 +00:00
success : true ,
issue : String ::from ( " " ) ,
2024-09-01 18:10:03 +00:00
} )
2024-04-20 15:03:39 +00:00
} ,
2024-05-12 12:40:06 +00:00
Err ( e ) = > {
2024-09-01 18:10:03 +00:00
error! ( Source = " Secret Store " ; " Failed to store secret for {service} and user {user_name}: {e}. " ) ;
Json ( StoreSecretResponse {
2024-05-12 12:40:06 +00:00
success : false ,
issue : e . to_string ( ) ,
2024-09-01 18:10:03 +00:00
} )
2024-04-20 15:03:39 +00:00
} ,
}
}
2024-09-01 18:10:03 +00:00
#[ derive(Deserialize) ]
struct StoreSecret {
destination : String ,
user_name : String ,
secret : EncryptedText ,
}
2024-04-20 15:03:39 +00:00
#[ derive(Serialize) ]
struct StoreSecretResponse {
success : bool ,
issue : String ,
2024-04-05 20:23:01 +00:00
}
2024-09-01 18:10:03 +00:00
#[ post( " /secrets/get " , data = " <request> " ) ]
fn get_secret ( _token : APIToken , request : Json < RequestSecret > ) -> Json < RequestedSecret > {
let user_name = request . user_name . as_str ( ) ;
let service = format! ( " mindwork-ai-studio:: {} " , request . destination ) ;
let entry = Entry ::new ( service . as_str ( ) , user_name ) . unwrap ( ) ;
2024-04-20 15:03:39 +00:00
let secret = entry . get_password ( ) ;
match secret {
2024-05-12 12:40:06 +00:00
Ok ( s ) = > {
2024-09-01 18:10:03 +00:00
info! ( Source = " Secret Store " ; " Secret for '{service}' and user '{user_name}' was retrieved successfully. " ) ;
// Encrypt the secret:
let encrypted_secret = match ENCRYPTION . encrypt ( s . as_str ( ) ) {
Ok ( e ) = > e ,
Err ( e ) = > {
error! ( Source = " Secret Store " ; " Failed to encrypt the secret: {e}. " ) ;
return Json ( RequestedSecret {
success : false ,
secret : EncryptedText ::new ( String ::from ( " " ) ) ,
issue : format ! ( " Failed to encrypt the secret: {e} " ) ,
} ) ;
} ,
} ;
Json ( RequestedSecret {
2024-05-12 12:40:06 +00:00
success : true ,
2024-09-01 18:10:03 +00:00
secret : encrypted_secret ,
2024-05-12 12:40:06 +00:00
issue : String ::from ( " " ) ,
2024-09-01 18:10:03 +00:00
} )
2024-04-20 15:03:39 +00:00
} ,
2024-05-12 12:40:06 +00:00
Err ( e ) = > {
2024-10-07 11:26:25 +00:00
if ! request . is_trying {
error! ( Source = " Secret Store " ; " Failed to retrieve secret for '{service}' and user '{user_name}': {e}. " ) ;
}
2024-09-01 18:10:03 +00:00
Json ( RequestedSecret {
2024-05-12 12:40:06 +00:00
success : false ,
2024-09-01 18:10:03 +00:00
secret : EncryptedText ::new ( String ::from ( " " ) ) ,
issue : format ! ( " Failed to retrieve secret for '{service}' and user '{user_name}': {e} " ) ,
} )
2024-04-20 15:03:39 +00:00
} ,
}
}
2024-09-01 18:10:03 +00:00
#[ derive(Deserialize) ]
struct RequestSecret {
destination : String ,
user_name : String ,
2024-10-07 11:26:25 +00:00
is_trying : bool ,
2024-09-01 18:10:03 +00:00
}
2024-04-20 15:03:39 +00:00
#[ derive(Serialize) ]
struct RequestedSecret {
success : bool ,
2024-09-01 18:10:03 +00:00
secret : EncryptedText ,
2024-04-20 15:03:39 +00:00
issue : String ,
}
2024-09-01 18:10:03 +00:00
#[ post( " /secrets/delete " , data = " <request> " ) ]
fn delete_secret ( _token : APIToken , request : Json < RequestSecret > ) -> Json < DeleteSecretResponse > {
let user_name = request . user_name . as_str ( ) ;
let service = format! ( " mindwork-ai-studio:: {} " , request . destination ) ;
let entry = Entry ::new ( service . as_str ( ) , user_name ) . unwrap ( ) ;
2024-08-24 18:05:39 +00:00
let result = entry . delete_credential ( ) ;
2024-07-28 14:55:38 +00:00
2024-04-20 15:03:39 +00:00
match result {
2024-05-12 12:40:06 +00:00
Ok ( _ ) = > {
2024-09-01 18:10:03 +00:00
warn! ( Source = " Secret Store " ; " Secret for {service} and user {user_name} was deleted successfully. " ) ;
Json ( DeleteSecretResponse {
2024-05-12 12:40:06 +00:00
success : true ,
2024-07-28 14:55:38 +00:00
was_entry_found : true ,
2024-05-12 12:40:06 +00:00
issue : String ::from ( " " ) ,
2024-09-01 18:10:03 +00:00
} )
2024-04-20 15:03:39 +00:00
} ,
2024-07-28 14:55:38 +00:00
Err ( NoEntry ) = > {
2024-09-01 18:10:03 +00:00
warn! ( Source = " Secret Store " ; " No secret for {service} and user {user_name} was found. " ) ;
Json ( DeleteSecretResponse {
2024-07-28 14:55:38 +00:00
success : true ,
was_entry_found : false ,
issue : String ::from ( " " ) ,
2024-09-01 18:10:03 +00:00
} )
2024-07-28 14:55:38 +00:00
}
2024-04-20 15:03:39 +00:00
2024-05-12 12:40:06 +00:00
Err ( e ) = > {
2024-09-01 18:10:03 +00:00
error! ( Source = " Secret Store " ; " Failed to delete secret for {service} and user {user_name}: {e}. " ) ;
Json ( DeleteSecretResponse {
2024-05-12 12:40:06 +00:00
success : false ,
2024-07-28 14:55:38 +00:00
was_entry_found : false ,
2024-05-12 12:40:06 +00:00
issue : e . to_string ( ) ,
2024-09-01 18:10:03 +00:00
} )
2024-04-20 15:03:39 +00:00
} ,
}
}
#[ derive(Serialize) ]
struct DeleteSecretResponse {
success : bool ,
2024-07-28 14:55:38 +00:00
was_entry_found : bool ,
2024-04-20 15:03:39 +00:00
issue : String ,
2024-05-03 20:55:21 +00:00
}
2024-09-01 18:10:03 +00:00
#[ post( " /clipboard/set " , data = " <encrypted_text> " ) ]
fn set_clipboard ( _token : APIToken , encrypted_text : EncryptedText ) -> Json < SetClipboardResponse > {
// Decrypt this text first:
let decrypted_text = match ENCRYPTION . decrypt ( & encrypted_text ) {
Ok ( text ) = > text ,
Err ( e ) = > {
error! ( Source = " Clipboard " ; " Failed to decrypt the text: {e}. " ) ;
return Json ( SetClipboardResponse {
success : false ,
issue : e ,
} )
} ,
} ;
2024-05-03 20:55:21 +00:00
let clipboard_result = Clipboard ::new ( ) ;
let mut clipboard = match clipboard_result {
Ok ( clipboard ) = > clipboard ,
2024-05-12 12:40:06 +00:00
Err ( e ) = > {
2024-09-01 18:10:03 +00:00
error! ( Source = " Clipboard " ; " Failed to get the clipboard instance: {e}. " ) ;
return Json ( SetClipboardResponse {
2024-05-12 12:40:06 +00:00
success : false ,
issue : e . to_string ( ) ,
2024-09-01 18:10:03 +00:00
} )
2024-05-03 20:55:21 +00:00
} ,
} ;
2024-09-01 18:10:03 +00:00
let set_text_result = clipboard . set_text ( decrypted_text ) ;
2024-05-03 20:55:21 +00:00
match set_text_result {
2024-05-12 12:40:06 +00:00
Ok ( _ ) = > {
2024-09-01 18:10:03 +00:00
debug! ( Source = " Clipboard " ; " Text was set to the clipboard successfully. " ) ;
Json ( SetClipboardResponse {
2024-05-12 12:40:06 +00:00
success : true ,
issue : String ::from ( " " ) ,
2024-09-01 18:10:03 +00:00
} )
2024-05-03 20:55:21 +00:00
} ,
2024-05-12 12:40:06 +00:00
Err ( e ) = > {
2024-09-01 18:10:03 +00:00
error! ( Source = " Clipboard " ; " Failed to set text to the clipboard: {e}. " ) ;
Json ( SetClipboardResponse {
2024-05-12 12:40:06 +00:00
success : false ,
issue : e . to_string ( ) ,
2024-09-01 18:10:03 +00:00
} )
2024-05-03 20:55:21 +00:00
} ,
}
}
#[ derive(Serialize) ]
struct SetClipboardResponse {
success : bool ,
issue : String ,
2024-04-05 20:23:01 +00:00
}