diff --git a/Cargo.lock b/Cargo.lock index f7b2f07..c21f8ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0346d8c1f762b41b458ed3145eea914966bb9ad20b9be0d6d463b20d45586370" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more 0.99.20", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.11.0" @@ -144,7 +159,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2", + "socket2 0.5.10", "tokio", "tracing", ] @@ -206,7 +221,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.5.10", "time", "tracing", "url", @@ -248,7 +263,6 @@ dependencies = [ "cfg-if", "getrandom 0.3.3", "once_cell", - "serde", "version_check", "zerocopy", ] @@ -395,7 +409,7 @@ checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", - "fastrand", + "fastrand 2.3.0", "futures-lite", "pin-project-lite", "slab", @@ -992,6 +1006,22 @@ dependencies = [ "serde", ] +[[package]] +name = "email-encoding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a87260449b06739ee78d6281c68d2a0ff3e3af64a78df63d3a1aeb3c06997c8a" +dependencies = [ + "base64 0.22.1", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1078,6 +1108,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1206,7 +1245,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ - "fastrand", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -1258,12 +1297,13 @@ dependencies = [ name = "gbserver" version = "0.1.0" dependencies = [ + "actix-cors", "actix-multipart", "actix-web", "chrono", "dotenv", "imap", - "jmap-client", + "lettre", "log", "mailparse", "minio", @@ -1440,6 +1480,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.12" @@ -1462,17 +1513,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1492,7 +1532,7 @@ dependencies = [ "bytes", "futures-core", "http 1.3.1", - "http-body 1.0.1", + "http-body", "pin-project-lite", ] @@ -1508,30 +1548,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.6.0" @@ -1543,7 +1559,7 @@ dependencies = [ "futures-util", "h2 0.4.10", "http 1.3.1", - "http-body 1.0.1", + "http-body", "httparse", "httpdate", "itoa", @@ -1553,20 +1569,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -1575,7 +1577,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-util", "native-tls", "tokio", @@ -1595,13 +1597,13 @@ dependencies = [ "futures-core", "futures-util", "http 1.3.1", - "http-body 1.0.1", - "hyper 1.6.0", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1723,6 +1725,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "1.0.3" @@ -1785,6 +1797,15 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1837,28 +1858,6 @@ dependencies = [ "syn 2.0.103", ] -[[package]] -name = "jmap-client" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c697483ad894a8184d0fd61848e057f86b16642049993b3e6a80c959dbc90a" -dependencies = [ - "ahash", - "async-stream", - "base64 0.13.1", - "chrono", - "futures-util", - "maybe-async", - "parking_lot", - "reqwest 0.11.27", - "rustls 0.22.4", - "rustls-pki-types", - "serde", - "serde_json", - "tokio", - "tokio-tungstenite", -] - [[package]] name = "jobserver" version = "0.1.33" @@ -1903,6 +1902,32 @@ dependencies = [ "spin", ] +[[package]] +name = "lettre" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" +dependencies = [ + "async-trait", + "base64 0.21.7", + "email-encoding", + "email_address", + "fastrand 1.9.0", + "futures-io", + "futures-util", + "hostname", + "httpdate", + "idna 0.3.0", + "mime", + "native-tls", + "nom 7.1.3", + "once_cell", + "quoted_printable", + "socket2 0.4.10", + "tokio", + "tokio-native-tls", +] + [[package]] name = "lexical-core" version = "0.7.6" @@ -1999,15 +2024,10 @@ dependencies = [ ] [[package]] -name = "maybe-async" -version = "0.2.10" +name = "match_cfg" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.103", -] +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "md-5" @@ -2065,7 +2085,7 @@ dependencies = [ "hex", "hmac", "http 1.3.1", - "hyper 1.6.0", + "hyper", "lazy_static", "log", "md5", @@ -2073,7 +2093,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "regex", - "reqwest 0.12.20", + "reqwest", "serde", "serde_json", "sha2", @@ -2358,7 +2378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand", + "fastrand 2.3.0", "futures-io", ] @@ -2576,49 +2596,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-rustls 0.24.1", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots 0.25.4", - "winreg", -] - [[package]] name = "reqwest" version = "0.12.20" @@ -2630,9 +2607,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.3.1", - "http-body 1.0.1", + "http-body", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-tls", "hyper-util", "js-sys", @@ -2644,7 +2621,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-native-tls", "tokio-util", @@ -2726,26 +2703,11 @@ version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "log", "ring", - "rustls-webpki 0.101.7", + "rustls-webpki", "sct", ] -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2774,17 +2736,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.21" @@ -2972,6 +2923,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.10" @@ -3051,7 +3012,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.21.12", + "rustls", "rustls-pemfile", "serde", "serde_json", @@ -3063,7 +3024,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.25.4", + "webpki-roots", ] [[package]] @@ -3265,12 +3226,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3291,34 +3246,13 @@ dependencies = [ "syn 2.0.103", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "fastrand", + "fastrand 2.3.0", "getrandom 0.3.3", "once_cell", "rustix", @@ -3423,7 +3357,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.52.0", ] @@ -3449,27 +3383,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.17" @@ -3481,22 +3394,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", - "rustls 0.22.4", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "tungstenite", - "webpki-roots 0.26.11", -] - [[package]] name = "tokio-util" version = "0.7.15" @@ -3519,7 +3416,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -3535,7 +3432,7 @@ dependencies = [ "bytes", "futures-util", "http 1.3.1", - "http-body 1.0.1", + "http-body", "iri-string", "pin-project-lite", "tower", @@ -3619,27 +3516,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.3.1", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.22.4", - "rustls-pki-types", - "sha1", - "thiserror", - "url", - "utf-8", -] - [[package]] name = "typenum" version = "1.18.0" @@ -3704,7 +3580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna", + "idna 1.0.3", "percent-encoding", ] @@ -3714,12 +3590,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3886,24 +3756,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.1", -] - -[[package]] -name = "webpki-roots" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "whoami" version = "1.6.0" @@ -4216,16 +4068,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 49995de..dd57b83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,17 @@ license = "AGPL" repository = "https://alm.pragmatismo.com.br/generalbots/gbserver" [dependencies] +actix-cors = "0.6" actix-multipart = "0.6" actix-web = "4" chrono = { version = "0.4", features = ["serde"] } dotenv = "0.15" -jmap-client = "0.3.2" +imap = "2.0" +lettre = { version = "0.10", features = ["smtp-transport", "builder", "tokio1", "tokio1-native-tls"] } log = "0.4" +mailparse = "0.13" minio = { git = "https://github.com/minio/minio-rs", branch = "master" } +native-tls = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] } @@ -23,6 +27,3 @@ tokio = { version = "1", features = ["full"] } tokio-stream = "0.1.17" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt"] } -imap = "2" -native-tls = "0.2" -mailparse = "0.13" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8d9bdf9..1206313 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use actix_cors::Cors; +use actix_web::http::header; use actix_web::{web, App, HttpServer}; use dotenv::dotenv; use sqlx::PgPool; @@ -14,27 +16,36 @@ async fn main() -> std::io::Result<()> { dotenv().ok(); let config = AppConfig::from_env(); - let db_url = config.database_url(); - let db = PgPool::connect(&db_url).await.unwrap(); + let db_url = config.database_url(); + //let db = PgPool::connect(&db_url).await.unwrap(); - let minio_client = init_minio(&config) - .await - .expect("Failed to initialize Minio"); + // let minio_client = init_minio(&config) + // .await + // .expect("Failed to initialize Minio"); let app_state = web::Data::new(AppState { - db: Some(db.clone()), + db: None, config: Some(config.clone()), - minio_client: Some(minio_client), + minio_client: None, }); - + // Start HTTP server HttpServer::new(move || { + let cors = Cors::default() + .allowed_origin("http://localhost:3000") // Your Next.js port + .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"]) + .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) + .allowed_header(header::CONTENT_TYPE) + .max_age(3600); App::new() + .wrap(cors) .app_data(app_state.clone()) .service(upload_file) .service(list_file) .service(save_click) .service(get_emails) + .service(list_emails) + .service(send_email) }) .bind((config.server.host.clone(), config.server.port))? .run() diff --git a/src/services/email.rs b/src/services/email.rs index 1553f01..86d2119 100644 --- a/src/services/email.rs +++ b/src/services/email.rs @@ -1,12 +1,13 @@ -use std::str::FromStr; +use crate::services::{config::EmailConfig, state::AppState}; +use actix_web::error::ErrorInternalServerError; +use actix_web::http::header::ContentType; +use actix_web::{web, HttpResponse, Result}; +use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport}; +use serde::{Deserialize, Serialize}; +use tokio_stream::StreamExt; +use mailparse::{parse_mail, MailHeaderMap}; // Added MailHeaderMap import +use imap::types::{Seq}; -use actix_web::{error::ErrorInternalServerError, http::header::ContentType, web, HttpResponse}; -use jmap_client::{ - client::Client, core::query::Filter, email, - identity::Property, mailbox::{self, Role}, - email::Property as EmailProperty -}; -use serde::Serialize; #[derive(Debug, Serialize)] pub struct EmailResponse { @@ -15,270 +16,260 @@ pub struct EmailResponse { pub email: String, pub subject: String, pub text: String, + date: String, + read: bool, + labels: Vec, } -use crate::services::state::AppState; - -async fn create_jmap_client( - state: &web::Data, -) -> Result<(Client, String, String, String), actix_web::Error> { - let config = state - .config - .as_ref() - .ok_or_else(|| actix_web::error::ErrorInternalServerError("Configuration not available"))?; - - let client = Client::new() - .credentials(( - config.email.username.as_ref(), - config.email.password.as_ref(), - )) - .connect(&config.email.server) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("JMAP connection error: {}", e)) - })?; - - // 2. Get account ID and email - let session = client.session(); - let (account_id, email) = session - .accounts() - .find_map(|account_id| { - let account = session.account(account_id).unwrap(); - Some((account_id.to_string(), account.name().to_string())) - }) +async fn internal_send_email(config: &EmailConfig, to: &str, subject: &str, body: &str) { + let email = Message::builder() + .from(config.from.parse().unwrap()) + .to(to.parse().unwrap()) + .subject(subject) + .body(body.to_string()) .unwrap(); - let identity = client - .identity_get("default", Some(vec![Property::Id, Property::Email])) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("JMAP connection error: {}", e)) - })?.unwrap(); + let creds = Credentials::new(config.username.clone(), config.password.clone()); - let identity_id = identity.id().unwrap(); - - println!("Account ID: {}", account_id); - println!("Email address: {}", email); - println!("IdentityID: {}", identity_id); - - Ok((client, account_id, email, String::from_str(identity_id)?)) + SmtpTransport::relay(&config.server) + .unwrap() + .port(config.port) + .credentials(creds) + .build() + .send(&email) + .unwrap(); } -#[actix_web::post("/emails/list")] +#[actix_web::get("/emails/list")] pub async fn list_emails( state: web::Data, ) -> Result>, actix_web::Error> { - let (client, account_id, email, identity_id) = create_jmap_client(&state).await?; + let _config = state + .config + .as_ref() + .ok_or_else(|| ErrorInternalServerError("Configuration not available"))?; - // Get inbox mailbox - let inbox_id = client - .mailbox_query( - mailbox::query::Filter::role(Role::Inbox).into(), - None::>, - ) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to query inbox: {}", e)) - })? - .take_ids() - .first() - .ok_or_else(|| actix_web::error::ErrorInternalServerError("Inbox not found"))? - .clone(); + // Establish connection + let tls = native_tls::TlsConnector::builder().build().map_err(|e| { + ErrorInternalServerError(format!("Failed to create TLS connector: {:?}", e)) + })?; - // Query emails in inbox - let email_ids = client - .email_query( - Filter::and([email::query::Filter::in_mailbox(&inbox_id)]).into(), - None::>, - ) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to query emails: {}", e)) - })? - .take_ids(); + let client = imap::connect( + (_config.email.server.as_str(), 993), + _config.email.server.as_str(), + &tls, + ) + .map_err(|e| ErrorInternalServerError(format!("Failed to connect to IMAP: {:?}", e)))?; + + // Login + let mut session = client + .login(&_config.email.username, &_config.email.password) + .map_err(|e| ErrorInternalServerError(format!("Login failed: {:?}", e)))?; + + // Select INBOX + session + .select("INBOX") + .map_err(|e| ErrorInternalServerError(format!("Failed to select INBOX: {:?}", e)))?; + + // Search for all messages + let messages = session + .search("ALL") + .map_err(|e| ErrorInternalServerError(format!("Failed to search emails: {:?}", e)))?; let mut email_list = Vec::new(); - for email_id in email_ids { - // Fetch email details - let email = client - .email_get( - &email_id, - [ - EmailProperty::Id, - EmailProperty::Subject, - EmailProperty::From, - EmailProperty::TextBody, - EmailProperty::Preview, - ] - .into(), - ) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to get emails: {}", e)) - })? - .unwrap(); - let from = email.from().unwrap().first(); - let (name, email_addr) = if let Some(addr) = from { - ( - addr.name().unwrap_or("Unknown").to_string(), - addr.email().to_string(), - ) - } else { - ("Unknown".to_string(), "unknown@example.com".to_string()) - }; + // Get last 20 messages + let recent_messages: Vec<_> = messages.iter().cloned().collect(); // Collect items into a Vec + let recent_messages: Vec = recent_messages.into_iter().rev().take(20).collect(); // Now you can reverse and take the last 20 + for seq in recent_messages { + // Fetch the entire message (headers + body) + let fetch_result = session.fetch(seq.to_string(), "RFC822"); + let messages = fetch_result + .map_err(|e| ErrorInternalServerError(format!("Failed to fetch email: {:?}", e)))?; - let text = email.preview().unwrap_or_default().to_string(); + for msg in messages.iter() { + let body = msg + .body() + .ok_or_else(|| ErrorInternalServerError("No body found"))?; - email_list.push(EmailResponse { - id: email.id().unwrap().to_string(), - name, - email: email_addr, - subject: email.subject().unwrap_or_default().to_string(), - text, - }); + // Parse the complete email message + let parsed = parse_mail(body) + .map_err(|e| ErrorInternalServerError(format!("Failed to parse email: {:?}", e)))?; + + // Extract headers + let headers = parsed.get_headers(); + let subject = headers.get_first_value("Subject").unwrap_or_default(); + let from = headers.get_first_value("From").unwrap_or_default(); + let date = headers.get_first_value("Date").unwrap_or_default(); + + // Extract body text (handles both simple and multipart emails) + let body_text = if let Some(body_part) = parsed.subparts.iter().find(|p| p.ctype.mimetype == "text/plain") { + body_part.get_body().unwrap_or_default() + } else { + parsed.get_body().unwrap_or_default() + }; + + // Create preview + let preview = body_text.lines().take(3).collect::>().join(" "); + let preview_truncated = if preview.len() > 150 { + format!("{}...", &preview[..150]) + } else { + preview + }; + + // Parse From field + let (from_name, from_email) = parse_from_field(&from); + + email_list.push(EmailResponse { + id: seq.to_string(), + name: from_name, + email: from_email, + subject: if subject.is_empty() { + "(No Subject)".to_string() + } else { + subject + }, + text: preview_truncated, + date: if date.is_empty() { + chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string() + } else { + date + }, + read: false, + labels: Vec::new(), + }); + } } + session + .logout() + .map_err(|e| ErrorInternalServerError(format!("Failed to logout: {:?}", e)))?; + Ok(web::Json(email_list)) } -#[actix_web::post("/emails/suggest-answer/{email_id}")] -pub async fn suggest_answer( - path: web::Path, - state: web::Data, -) -> Result { - let email_id = path.into_inner(); - let (client, account_id, email, identity_id) = create_jmap_client(&state).await?; - - // Fetch the specific email - let email = client - .email_get( - &email_id, - [ - EmailProperty::Id, - EmailProperty::Subject, - EmailProperty::From, - EmailProperty::TextBody, - EmailProperty::Preview, - ] - .into(), - ) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to get email: {}", e)) - })? - .into_iter() - .next() - .ok_or_else(|| actix_web::error::ErrorNotFound("Email not found"))?; - - let from = email.from().unwrap().first(); - let sender_info = if let Some(addr) = from { - format!("{} <{}>", addr.name().unwrap_or("Unknown"), addr.email()) - } else { - "Unknown sender".to_string() - }; - - let subject = email.subject().unwrap_or_default(); - let body_text = email.preview().unwrap_or_default(); - - let response = serde_json::json!({ - "suggested_response": "Thank you for your email. I will review this and get back to you shortly.", - "prompt": format!( - "Email from: {}\nSubject: {}\n\nBody:\n{}\n\n---\n\nPlease draft a professional response to this email.", - sender_info, subject, body_text - ) - }); - - Ok(HttpResponse::Ok().json(response)) +// Helper function to parse From field +fn parse_from_field(from: &str) -> (String, String) { + if let Some(start) = from.find('<') { + if let Some(end) = from.find('>') { + let email = from[start+1..end].trim().to_string(); + let name = from[..start].trim().trim_matches('"').to_string(); + return (name, email); + } + } + ("Unknown".to_string(), from.to_string()) } -#[actix_web::post("/emails/archive/{email_id}")] -pub async fn archive_email( - path: web::Path, - state: web::Data, -) -> Result { - let email_id = path.into_inner(); - let (client, account_id, email, identity_id) = create_jmap_client(&state).await?; +// #[actix_web::post("/emails/suggest-answer/{email_id}")] +// pub async fn suggest_answer( +// path: web::Path, +// state: web::Data, +// ) -> Result { +// let email_id = path.into_inner(); +// let config = state +// .config +// .as_ref() +// .ok_or_else(|| ErrorInternalServerError("Configuration not available"))?; - // Get Archive mailbox (or create if it doesn't exist) - let archive_id = match client - .mailbox_query( - mailbox::query::Filter::name("Archive").into(), - None::>, - ) - .await - { - Ok(mut result) => { - let ids = result.take_ids(); - if let Some(id) = ids.first() { - id.clone() - } else { - // Create Archive mailbox if it doesn't exist - client - .mailbox_create("Archive", None::, Role::Archive) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!( - "Failed to create archive mailbox: {}", - e - )) - })? - .take_id() - } - } - Err(e) => { - return Err(actix_web::error::ErrorInternalServerError(format!( - "Failed to query mailboxes: {}", - e - ))); - } - }; +// // let mut session = create_imap_session(&config.email).await?; - // Move email to Archive mailbox - client - .email_set_mailboxes(&email_id, [&archive_id]) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to archive email: {}", e)) - })?; +// // session +// // .select("INBOX") +// // .await +// // .map_err(|e| ErrorInternalServerError(format!("Failed to select INBOX: {:?}", e)))?; - Ok(HttpResponse::Ok().json(serde_json::json!({ - "message": "Email archived successfully", - "email_id": email_id, - "archive_mailbox_id": archive_id - }))) -} +// // let messages = session +// // .fetch(&email_id, "RFC822.HEADER BODY[TEXT]") +// // .await +// // .map_err(|e| ErrorInternalServerError(format!("Failed to fetch email: {:?}", e)))?; + +// // let msg = messages +// // .iter() +// // .next() +// // .ok_or_else(|| actix_web::error::ErrorNotFound("Email not found"))?; + +// // let header = msg +// // .header() +// // .ok_or_else(|| ErrorInternalServerError("No header found"))?; + +// // let body = msg +// // .text() +// // .ok_or_else(|| ErrorInternalServerError("No body found"))?; + +// // let header_str = String::from_utf8_lossy(header); +// // let mut subject = String::new(); +// // let mut from_info = String::new(); + +// // for line in header_str.lines() { +// // if line.starts_with("Subject: ") { +// // subject = line.strip_prefix("Subject: ").unwrap_or("").to_string(); +// // } else if line.starts_with("From: ") { +// // from_info = line.strip_prefix("From: ").unwrap_or("").to_string(); +// // } +// // } + +// // let body_text = String::from_utf8_lossy(body); + +// // let response = serde_json::json!({ +// // "suggested_response": "Thank you for your email. I will review this and get back to you shortly.", +// // "prompt": format!( +// // "Email from: {}\nSubject: {}\n\nBody:\n{}\n\n---\n\nPlease draft a professional response to this email.", +// // from_info, subject, body_text.lines().take(20).collect::>().join("\n") +// // ) +// // }); + +// // session.logout().await.ok(); +// //Ok(HttpResponse::Ok().json(response)) +// } + +// #[actix_web::post("/emails/archive/{email_id}")] +// pub async fn archive_email( +// path: web::Path, +// state: web::Data, +// ) -> Result { +// let email_id = path.into_inner(); +// let config = state +// .config +// .as_ref() +// .ok_or_else(|| ErrorInternalServerError("Configuration not available"))?; + +// let mut session = create_imap_session(&config.email).await?; + +// session +// .select("INBOX") +// .await +// .map_err(|e| ErrorInternalServerError(format!("Failed to select INBOX: {:?}", e)))?; + +// // Create Archive folder if it doesn't exist +// session.create("Archive").await.ok(); // Ignore error if folder exists + +// // Move email to Archive folder +// session.mv(&email_id, "Archive").await.map_err(|e| { +// ErrorInternalServerError(format!("Failed to move email to archive: {:?}", e)) +// })?; + +// session.logout().await.ok(); + +// Ok(HttpResponse::Ok().json(serde_json::json!({ +// "message": "Email archived successfully", +// "email_id": email_id, +// "archive_folder": "Archive" +// }))) +// } #[actix_web::post("/emails/send")] pub async fn send_email( payload: web::Json<(String, String, String)>, state: web::Data, ) -> Result { - // Destructure the tuple into individual components let (to, subject, body) = payload.into_inner(); println!("To: {}", to); println!("Subject: {}", subject); println!("Body: {}", body); - let (client, account_id, email, identity_id) = create_jmap_client(&state).await?; - - let email_submission = client - .email_submission_create("111", account_id) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to create email: {}", e)) - })?; - - let email_id = email_submission.email_id().unwrap(); - println!("Email-ID: {}", email_id); - - client - .email_submission_create(email_id, identity_id) - .await - .map_err(|e| { - actix_web::error::ErrorInternalServerError(format!("Failed to send email: {}", e)) - })?; + // Send via SMTP + internal_send_email(&state.config.clone().unwrap().email, &to, &subject, &body).await; Ok(HttpResponse::Ok().finish()) } diff --git a/src/templates/containers/desktop/desktop.sh b/src/templates/containers/desktop/desktop.sh index 724a514..6a3c9a4 100644 --- a/src/templates/containers/desktop/desktop.sh +++ b/src/templates/containers/desktop/desktop.sh @@ -42,7 +42,6 @@ sudo apt install gnome-tweaks QT_IM_MODULE=cedilla " - - -sudo iptables -t nat -A PREROUTING -p tcp --dport 3389 -j DNAT --to-destination $CONTAINER_IP:3389 -sudo iptables -A FORWARD -p tcp -d $CONTAINER_IP --dport 3389 -j ACCEPT \ No newline at end of file +port=3389 +lxc config device remove "$PARAM_TENANT"-desktop "port-$port" 2>/dev/null || true +lxc config device add "$PARAM_TENANT"-desktop "port-$port" proxy listen=tcp:0.0.0.0:$port connect=tcp:127.0.0.1:$port