- E-mail GET API.
Some checks are pending
GBCI / build (push) Waiting to run

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-07-04 10:39:31 -03:00
parent 263eab2d9c
commit 986d3b58ea
5 changed files with 386 additions and 542 deletions

428
Cargo.lock generated
View file

@ -19,6 +19,21 @@ dependencies = [
"tracing", "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]] [[package]]
name = "actix-http" name = "actix-http"
version = "3.11.0" version = "3.11.0"
@ -144,7 +159,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"mio", "mio",
"socket2", "socket2 0.5.10",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -206,7 +221,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"smallvec", "smallvec",
"socket2", "socket2 0.5.10",
"time", "time",
"tracing", "tracing",
"url", "url",
@ -248,7 +263,6 @@ dependencies = [
"cfg-if", "cfg-if",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"serde",
"version_check", "version_check",
"zerocopy", "zerocopy",
] ]
@ -395,7 +409,7 @@ checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa"
dependencies = [ dependencies = [
"async-task", "async-task",
"concurrent-queue", "concurrent-queue",
"fastrand", "fastrand 2.3.0",
"futures-lite", "futures-lite",
"pin-project-lite", "pin-project-lite",
"slab", "slab",
@ -992,6 +1006,22 @@ dependencies = [
"serde", "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]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -1078,6 +1108,15 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.3.0"
@ -1206,7 +1245,7 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
dependencies = [ dependencies = [
"fastrand", "fastrand 2.3.0",
"futures-core", "futures-core",
"futures-io", "futures-io",
"parking", "parking",
@ -1258,12 +1297,13 @@ dependencies = [
name = "gbserver" name = "gbserver"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-cors",
"actix-multipart", "actix-multipart",
"actix-web", "actix-web",
"chrono", "chrono",
"dotenv", "dotenv",
"imap", "imap",
"jmap-client", "lettre",
"log", "log",
"mailparse", "mailparse",
"minio", "minio",
@ -1440,6 +1480,17 @@ dependencies = [
"windows-sys 0.59.0", "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]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "0.2.12"
@ -1462,17 +1513,6 @@ dependencies = [
"itoa", "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]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.1" version = "1.0.1"
@ -1492,7 +1532,7 @@ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1", "http-body",
"pin-project-lite", "pin-project-lite",
] ]
@ -1508,30 +1548,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 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]] [[package]]
name = "hyper" name = "hyper"
version = "1.6.0" version = "1.6.0"
@ -1543,7 +1559,7 @@ dependencies = [
"futures-util", "futures-util",
"h2 0.4.10", "h2 0.4.10",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1", "http-body",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@ -1553,20 +1569,6 @@ dependencies = [
"want", "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]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.6.0" version = "0.6.0"
@ -1575,7 +1577,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"bytes", "bytes",
"http-body-util", "http-body-util",
"hyper 1.6.0", "hyper",
"hyper-util", "hyper-util",
"native-tls", "native-tls",
"tokio", "tokio",
@ -1595,13 +1597,13 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1", "http-body",
"hyper 1.6.0", "hyper",
"ipnet", "ipnet",
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.5.10",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@ -1723,6 +1725,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 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]] [[package]]
name = "idna" name = "idna"
version = "1.0.3" version = "1.0.3"
@ -1785,6 +1797,15 @@ dependencies = [
"hashbrown 0.15.4", "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]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.11.0" version = "2.11.0"
@ -1837,28 +1858,6 @@ dependencies = [
"syn 2.0.103", "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]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.33" version = "0.1.33"
@ -1903,6 +1902,32 @@ dependencies = [
"spin", "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]] [[package]]
name = "lexical-core" name = "lexical-core"
version = "0.7.6" version = "0.7.6"
@ -1999,15 +2024,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "maybe-async" name = "match_cfg"
version = "0.2.10" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
]
[[package]] [[package]]
name = "md-5" name = "md-5"
@ -2065,7 +2085,7 @@ dependencies = [
"hex", "hex",
"hmac", "hmac",
"http 1.3.1", "http 1.3.1",
"hyper 1.6.0", "hyper",
"lazy_static", "lazy_static",
"log", "log",
"md5", "md5",
@ -2073,7 +2093,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"rand 0.8.5", "rand 0.8.5",
"regex", "regex",
"reqwest 0.12.20", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -2358,7 +2378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"fastrand", "fastrand 2.3.0",
"futures-io", "futures-io",
] ]
@ -2576,49 +2596,6 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.20" version = "0.12.20"
@ -2630,9 +2607,9 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.6.0", "hyper",
"hyper-tls", "hyper-tls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
@ -2644,7 +2621,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper 1.0.2", "sync_wrapper",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-util", "tokio-util",
@ -2726,26 +2703,11 @@ version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [ dependencies = [
"log",
"ring", "ring",
"rustls-webpki 0.101.7", "rustls-webpki",
"sct", "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]] [[package]]
name = "rustls-pemfile" name = "rustls-pemfile"
version = "1.0.4" version = "1.0.4"
@ -2774,17 +2736,6 @@ dependencies = [
"untrusted", "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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.21"
@ -2972,6 +2923,16 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 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]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.10" version = "0.5.10"
@ -3051,7 +3012,7 @@ dependencies = [
"once_cell", "once_cell",
"paste", "paste",
"percent-encoding", "percent-encoding",
"rustls 0.21.12", "rustls",
"rustls-pemfile", "rustls-pemfile",
"serde", "serde",
"serde_json", "serde_json",
@ -3063,7 +3024,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"url", "url",
"webpki-roots 0.25.4", "webpki-roots",
] ]
[[package]] [[package]]
@ -3265,12 +3226,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "sync_wrapper" name = "sync_wrapper"
version = "1.0.2" version = "1.0.2"
@ -3291,34 +3246,13 @@ dependencies = [
"syn 2.0.103", "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]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.20.0" version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [ dependencies = [
"fastrand", "fastrand 2.3.0",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix", "rustix",
@ -3423,7 +3357,7 @@ dependencies = [
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2 0.5.10",
"tokio-macros", "tokio-macros",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -3449,27 +3383,6 @@ dependencies = [
"tokio", "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]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.17" version = "0.1.17"
@ -3481,22 +3394,6 @@ dependencies = [
"tokio", "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]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.15"
@ -3519,7 +3416,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"pin-project-lite", "pin-project-lite",
"sync_wrapper 1.0.2", "sync_wrapper",
"tokio", "tokio",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@ -3535,7 +3432,7 @@ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1", "http-body",
"iri-string", "iri-string",
"pin-project-lite", "pin-project-lite",
"tower", "tower",
@ -3619,27 +3516,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 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]] [[package]]
name = "typenum" name = "typenum"
version = "1.18.0" version = "1.18.0"
@ -3704,7 +3580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna 1.0.3",
"percent-encoding", "percent-encoding",
] ]
@ -3714,12 +3590,6 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
@ -3886,24 +3756,6 @@ version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 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]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.0" version = "1.6.0"
@ -4216,16 +4068,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 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]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen-rt"
version = "0.39.0" version = "0.39.0"

View file

@ -8,13 +8,17 @@ license = "AGPL"
repository = "https://alm.pragmatismo.com.br/generalbots/gbserver" repository = "https://alm.pragmatismo.com.br/generalbots/gbserver"
[dependencies] [dependencies]
actix-cors = "0.6"
actix-multipart = "0.6" actix-multipart = "0.6"
actix-web = "4" actix-web = "4"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
dotenv = "0.15" 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" log = "0.4"
mailparse = "0.13"
minio = { git = "https://github.com/minio/minio-rs", branch = "master" } minio = { git = "https://github.com/minio/minio-rs", branch = "master" }
native-tls = "0.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] } sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] }
@ -23,6 +27,3 @@ tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1.17" tokio-stream = "0.1.17"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt"] } tracing-subscriber = { version = "0.3", features = ["fmt"] }
imap = "2"
native-tls = "0.2"
mailparse = "0.13"

View file

@ -1,3 +1,5 @@
use actix_cors::Cors;
use actix_web::http::header;
use actix_web::{web, App, HttpServer}; use actix_web::{web, App, HttpServer};
use dotenv::dotenv; use dotenv::dotenv;
use sqlx::PgPool; use sqlx::PgPool;
@ -14,27 +16,36 @@ async fn main() -> std::io::Result<()> {
dotenv().ok(); dotenv().ok();
let config = AppConfig::from_env(); let config = AppConfig::from_env();
let db_url = config.database_url(); let db_url = config.database_url();
let db = PgPool::connect(&db_url).await.unwrap(); //let db = PgPool::connect(&db_url).await.unwrap();
let minio_client = init_minio(&config) // let minio_client = init_minio(&config)
.await // .await
.expect("Failed to initialize Minio"); // .expect("Failed to initialize Minio");
let app_state = web::Data::new(AppState { let app_state = web::Data::new(AppState {
db: Some(db.clone()), db: None,
config: Some(config.clone()), config: Some(config.clone()),
minio_client: Some(minio_client), minio_client: None,
}); });
// Start HTTP server // Start HTTP server
HttpServer::new(move || { 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() App::new()
.wrap(cors)
.app_data(app_state.clone()) .app_data(app_state.clone())
.service(upload_file) .service(upload_file)
.service(list_file) .service(list_file)
.service(save_click) .service(save_click)
.service(get_emails) .service(get_emails)
.service(list_emails)
.service(send_email)
}) })
.bind((config.server.host.clone(), config.server.port))? .bind((config.server.host.clone(), config.server.port))?
.run() .run()

View file

@ -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)] #[derive(Debug, Serialize)]
pub struct EmailResponse { pub struct EmailResponse {
@ -15,270 +16,260 @@ pub struct EmailResponse {
pub email: String, pub email: String,
pub subject: String, pub subject: String,
pub text: String, pub text: String,
date: String,
read: bool,
labels: Vec<String>,
} }
use crate::services::state::AppState; async fn internal_send_email(config: &EmailConfig, to: &str, subject: &str, body: &str) {
let email = Message::builder()
async fn create_jmap_client( .from(config.from.parse().unwrap())
state: &web::Data<AppState>, .to(to.parse().unwrap())
) -> Result<(Client, String, String, String), actix_web::Error> { .subject(subject)
let config = state .body(body.to_string())
.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()))
})
.unwrap(); .unwrap();
let identity = client let creds = Credentials::new(config.username.clone(), config.password.clone());
.identity_get("default", Some(vec![Property::Id, Property::Email]))
.await
.map_err(|e| {
actix_web::error::ErrorInternalServerError(format!("JMAP connection error: {}", e))
})?.unwrap();
let identity_id = identity.id().unwrap(); SmtpTransport::relay(&config.server)
.unwrap()
println!("Account ID: {}", account_id); .port(config.port)
println!("Email address: {}", email); .credentials(creds)
println!("IdentityID: {}", identity_id); .build()
.send(&email)
Ok((client, account_id, email, String::from_str(identity_id)?)) .unwrap();
} }
#[actix_web::post("/emails/list")] #[actix_web::get("/emails/list")]
pub async fn list_emails( pub async fn list_emails(
state: web::Data<AppState>, state: web::Data<AppState>,
) -> Result<web::Json<Vec<EmailResponse>>, actix_web::Error> { ) -> Result<web::Json<Vec<EmailResponse>>, 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 // Establish connection
let inbox_id = client let tls = native_tls::TlsConnector::builder().build().map_err(|e| {
.mailbox_query( ErrorInternalServerError(format!("Failed to create TLS connector: {:?}", e))
mailbox::query::Filter::role(Role::Inbox).into(), })?;
None::<Vec<_>>,
)
.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();
// Query emails in inbox let client = imap::connect(
let email_ids = client (_config.email.server.as_str(), 993),
.email_query( _config.email.server.as_str(),
Filter::and([email::query::Filter::in_mailbox(&inbox_id)]).into(), &tls,
None::<Vec<_>>, )
) .map_err(|e| ErrorInternalServerError(format!("Failed to connect to IMAP: {:?}", e)))?;
.await
.map_err(|e| { // Login
actix_web::error::ErrorInternalServerError(format!("Failed to query emails: {}", e)) let mut session = client
})? .login(&_config.email.username, &_config.email.password)
.take_ids(); .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(); 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(); // Get last 20 messages
let (name, email_addr) = if let Some(addr) = from { let recent_messages: Vec<_> = messages.iter().cloned().collect(); // Collect items into a Vec
( let recent_messages: Vec<Seq> = recent_messages.into_iter().rev().take(20).collect(); // Now you can reverse and take the last 20
addr.name().unwrap_or("Unknown").to_string(), for seq in recent_messages {
addr.email().to_string(), // Fetch the entire message (headers + body)
) let fetch_result = session.fetch(seq.to_string(), "RFC822");
} else { let messages = fetch_result
("Unknown".to_string(), "unknown@example.com".to_string()) .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 { // Parse the complete email message
id: email.id().unwrap().to_string(), let parsed = parse_mail(body)
name, .map_err(|e| ErrorInternalServerError(format!("Failed to parse email: {:?}", e)))?;
email: email_addr,
subject: email.subject().unwrap_or_default().to_string(), // Extract headers
text, 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::<Vec<_>>().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)) Ok(web::Json(email_list))
} }
#[actix_web::post("/emails/suggest-answer/{email_id}")] // Helper function to parse From field
pub async fn suggest_answer( fn parse_from_field(from: &str) -> (String, String) {
path: web::Path<String>, if let Some(start) = from.find('<') {
state: web::Data<AppState>, if let Some(end) = from.find('>') {
) -> Result<HttpResponse, actix_web::Error> { let email = from[start+1..end].trim().to_string();
let email_id = path.into_inner(); let name = from[..start].trim().trim_matches('"').to_string();
let (client, account_id, email, identity_id) = create_jmap_client(&state).await?; return (name, email);
}
// Fetch the specific email }
let email = client ("Unknown".to_string(), from.to_string())
.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))
} }
#[actix_web::post("/emails/archive/{email_id}")] // #[actix_web::post("/emails/suggest-answer/{email_id}")]
pub async fn archive_email( // pub async fn suggest_answer(
path: web::Path<String>, // path: web::Path<String>,
state: web::Data<AppState>, // state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> { // ) -> Result<HttpResponse, actix_web::Error> {
let email_id = path.into_inner(); // let email_id = path.into_inner();
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 Archive mailbox (or create if it doesn't exist) // // let mut session = create_imap_session(&config.email).await?;
let archive_id = match client
.mailbox_query(
mailbox::query::Filter::name("Archive").into(),
None::<Vec<_>>,
)
.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::<String>, 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
)));
}
};
// Move email to Archive mailbox // // session
client // // .select("INBOX")
.email_set_mailboxes(&email_id, [&archive_id]) // // .await
.await // // .map_err(|e| ErrorInternalServerError(format!("Failed to select INBOX: {:?}", e)))?;
.map_err(|e| {
actix_web::error::ErrorInternalServerError(format!("Failed to archive email: {}", e))
})?;
Ok(HttpResponse::Ok().json(serde_json::json!({ // // let messages = session
"message": "Email archived successfully", // // .fetch(&email_id, "RFC822.HEADER BODY[TEXT]")
"email_id": email_id, // // .await
"archive_mailbox_id": archive_id // // .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::<Vec<_>>().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<String>,
// state: web::Data<AppState>,
// ) -> Result<HttpResponse, actix_web::Error> {
// 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")] #[actix_web::post("/emails/send")]
pub async fn send_email( pub async fn send_email(
payload: web::Json<(String, String, String)>, payload: web::Json<(String, String, String)>,
state: web::Data<AppState>, state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> { ) -> Result<HttpResponse, actix_web::Error> {
// Destructure the tuple into individual components
let (to, subject, body) = payload.into_inner(); let (to, subject, body) = payload.into_inner();
println!("To: {}", to); println!("To: {}", to);
println!("Subject: {}", subject); println!("Subject: {}", subject);
println!("Body: {}", body); println!("Body: {}", body);
let (client, account_id, email, identity_id) = create_jmap_client(&state).await?; // Send via SMTP
internal_send_email(&state.config.clone().unwrap().email, &to, &subject, &body).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))
})?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }

View file

@ -42,7 +42,6 @@ sudo apt install gnome-tweaks
QT_IM_MODULE=cedilla QT_IM_MODULE=cedilla
" "
port=3389
lxc config device remove "$PARAM_TENANT"-desktop "port-$port" 2>/dev/null || true
sudo iptables -t nat -A PREROUTING -p tcp --dport 3389 -j DNAT --to-destination $CONTAINER_IP:3389 lxc config device add "$PARAM_TENANT"-desktop "port-$port" proxy listen=tcp:0.0.0.0:$port connect=tcp:127.0.0.1:$port
sudo iptables -A FORWARD -p tcp -d $CONTAINER_IP --dport 3389 -j ACCEPT