From 0b866c81ffbb05e1d6a93677febadadb75b0c0ef Mon Sep 17 00:00:00 2001 From: rustmailer Date: Thu, 23 Apr 2026 21:45:34 +0800 Subject: [PATCH] refactor(workspace): decompose project into multiple crates --- Cargo.lock | 959 +++++++++--------- Cargo.toml | 64 +- crates/admin/Cargo.toml | 11 + .../admin/src/main.rs | 9 +- crates/cli/Cargo.toml | 24 + {src/modules/cli => crates/cli/src}/auth.rs | 6 +- .../modules/cli => crates/cli/src}/eml/mod.rs | 8 +- .../bichonctl.rs => crates/cli/src/main.rs | 46 +- .../cli => crates/cli/src}/mbox/gmail.rs | 0 .../cli => crates/cli/src}/mbox/mod.rs | 10 +- .../cli => crates/cli/src}/mbox/reader.rs | 4 +- .../cli/src}/pst/encoding/mod.rs | 0 .../modules/cli => crates/cli/src}/pst/mod.rs | 9 +- {src/modules/cli => crates/cli/src}/sender.rs | 5 +- .../cli => crates/cli/src}/thunderbird/mod.rs | 3 +- crates/core/Cargo.toml | 67 ++ .../core/src}/account/entity.rs | 24 +- .../core/src}/account/grant.rs | 9 +- .../core/src}/account/migration.rs | 73 +- .../core/src}/account/mod.rs | 0 .../core/src}/account/old_state.rs | 14 +- .../core/src}/account/payload.rs | 34 +- .../core/src}/account/since.rs | 18 +- .../core/src}/account/state.rs | 24 +- .../core/src}/account/view.rs | 7 +- .../cli => crates/core/src}/admin/meta.rs | 2 +- .../cli => crates/core/src}/admin/mod.rs | 0 .../core/src}/autoconfig/entity.rs | 12 +- .../core/src}/autoconfig/load.rs | 6 +- .../core/src}/autoconfig/mod.rs | 8 +- .../core/src}/autoconfig/tests.rs | 0 .../cache/imap/download/download_folders.rs | 2 +- .../src}/cache/imap/download/download_type.rs | 2 +- .../core/src}/cache/imap/download/flow.rs | 2 +- .../core/src}/cache/imap/download/mod.rs | 2 +- .../core/src}/cache/imap/download/rebuild.rs | 2 +- .../core/src}/cache/imap/mailbox.rs | 12 +- .../core/src}/cache/imap/mod.rs | 16 +- .../core/src}/cache/imap/task.rs | 12 +- {src/modules => crates/core/src}/cache/mod.rs | 2 +- crates/core/src/common/auth.rs | 143 +++ .../modules => crates/core/src}/common/mod.rs | 72 +- crates/core/src/common/paginated.rs | 180 ++++ .../core/src}/common/periodic.rs | 2 +- .../core/src}/common/rustls.rs | 2 +- .../core/src}/common/signal.rs | 2 +- .../core/src}/context/controller.rs | 2 +- .../core/src}/context/executors.rs | 6 +- .../core/src}/context/mod.rs | 3 +- .../core/src}/dashboard/mod.rs | 26 +- .../core/src}/database/manager.rs | 14 +- .../core/src}/database/mod.rs | 52 +- .../core/src}/envelope/extractor.rs | 24 +- .../core/src}/envelope/mod.rs | 0 .../core/src}/envelope/utils.rs | 2 +- crates/core/src/error/code.rs | 44 + crates/core/src/error/mod.rs | 19 + .../core/src}/imap/capabilities.rs | 6 +- .../core/src}/imap/client.rs | 16 +- .../core/src}/imap/executor.rs | 16 +- .../core/src}/imap/manager.rs | 18 +- {src/modules => crates/core/src}/imap/mod.rs | 0 .../core/src}/imap/oauth2.rs | 0 .../core/src}/imap/session.rs | 0 .../modules => crates/core/src}/imap/stats.rs | 2 +- .../modules => crates/core/src}/imap/tests.rs | 2 +- .../modules => crates/core/src}/import/mod.rs | 13 +- crates/core/src/lib.rs | 23 + .../core/src}/logger/file.rs | 6 +- .../modules => crates/core/src}/logger/mod.rs | 4 +- .../core/src}/mailbox/delete.rs | 2 +- .../core/src}/mailbox/list.rs | 14 +- .../core/src}/mailbox/mod.rs | 0 .../core/src}/message/append.rs | 7 +- .../core/src}/message/attachment.rs | 7 +- .../core/src}/message/contacts.rs | 4 +- .../core/src}/message/content.rs | 23 +- .../core/src}/message/delete.rs | 6 +- .../core/src}/message/list.rs | 7 +- .../core/src}/message/mod.rs | 0 .../core/src}/message/search.rs | 26 +- .../core/src}/message/tags.rs | 11 +- .../core/src}/oauth2/entity.rs | 27 +- .../core/src}/oauth2/flow.rs | 13 +- .../modules => crates/core/src}/oauth2/mod.rs | 0 .../core/src}/oauth2/pending.rs | 2 +- .../core/src}/oauth2/refresh/mod.rs | 8 +- .../core/src}/oauth2/task.rs | 2 +- .../core/src}/oauth2/token.rs | 12 +- .../core/src}/settings/cli.rs | 2 +- .../core/src}/settings/dir.rs | 6 +- .../core/src}/settings/io.rs | 0 .../core/src}/settings/mod.rs | 7 +- .../core/src}/settings/proxy.rs | 7 +- .../core/src}/settings/system.rs | 5 - .../core/src}/store/envelope.rs | 5 +- {src/modules => crates/core/src}/store/mod.rs | 0 .../core/src}/store/storage.rs | 2 +- .../core/src}/store/tantivy/attachment.rs | 17 +- .../core/src}/store/tantivy/envelope.rs | 18 +- .../core/src}/store/tantivy/fields.rs | 0 .../core/src}/store/tantivy/mod.rs | 0 .../core/src}/store/tantivy/model.rs | 9 +- .../core/src}/store/tantivy/schema.rs | 2 +- {src/modules => crates/core/src}/tasks/mod.rs | 6 +- {src/modules => crates/core/src}/token/mod.rs | 23 +- .../core/src}/token/payload.rs | 9 +- .../modules => crates/core/src}/token/view.rs | 9 +- {src/modules => crates/core/src}/users/acl.rs | 13 +- .../core/src}/users/manager.rs | 2 +- .../core/src}/users/minimal.rs | 7 +- {src/modules => crates/core/src}/users/mod.rs | 13 +- .../core/src}/users/payload.rs | 24 +- .../core/src}/users/permissions.rs | 4 +- .../modules => crates/core/src}/users/role.rs | 13 +- .../modules => crates/core/src}/users/view.rs | 7 +- .../core/src}/utils/encrypt.rs | 6 +- .../modules => crates/core/src}/utils/html.rs | 0 {src/modules => crates/core/src}/utils/mod.rs | 20 +- {src/modules => crates/core/src}/utils/net.rs | 8 +- .../core/src}/utils/rate_limit/mod.rs | 2 +- .../core/src}/utils/shutdown.rs | 0 {src/modules => crates/core/src}/utils/tls.rs | 4 +- .../core/src}/version/mod.rs | 16 +- crates/server/Cargo.toml | 33 + build.rs => crates/server/build.rs | 0 crates/server/src/common/auth.rs | 155 +++ .../server/src}/common/error.rs | 2 +- .../server/src}/common/log.rs | 0 crates/server/src/common/mod.rs | 108 ++ .../server/src/common}/status.rs | 2 +- .../server/src}/common/timeout.rs | 4 +- .../server/src}/common/tls.rs | 8 +- .../server/src}/common/validator.rs | 0 .../server/src}/error/code.rs | 45 +- .../server/src}/error/handler.rs | 5 +- .../server/src}/error/mod.rs | 18 +- {src => crates/server/src}/main.rs | 27 +- .../server/src}/rest/api/access_token.rs | 18 +- .../server/src}/rest/api/account.rs | 42 +- .../server/src}/rest/api/attachment.rs | 42 +- .../server/src}/rest/api/auto_config.rs | 18 +- .../server/src}/rest/api/import.rs | 14 +- .../server/src}/rest/api/mailbox.rs | 18 +- .../server/src}/rest/api/message.rs | 74 +- .../server/src}/rest/api/mod.rs | 7 +- .../server/src}/rest/api/oauth2.rs | 39 +- .../server/src}/rest/api/system.rs | 43 +- .../server/src}/rest/api/users.rs | 46 +- .../server/src}/rest/assets.rs | 2 +- .../modules => crates/server/src}/rest/mod.rs | 27 +- .../server/src}/rest/public/login.rs | 2 +- .../server/src}/rest/public/mod.rs | 0 .../server/src}/rest/public/oauth2.rs | 2 +- .../server/src}/rest/public/status.rs | 3 +- crates/smtp/Cargo.toml | 25 + crates/smtp/src/lib.rs | 6 + .../smtp => crates/smtp/src}/server.rs | 91 +- .../smtp => crates/smtp/src}/stream.rs | 0 .../modules/smtp => crates/smtp/src}/tests.rs | 0 {src/modules/smtp => crates/smtp/src}/tls.rs | 2 +- src/lib.rs | 19 - src/modules/cli/mod.rs | 56 - src/modules/common/auth.rs | 283 ------ src/modules/common/paginated.rs | 72 -- src/modules/mod.rs | 43 - src/modules/rest/response.rs | 84 -- src/modules/smtp/mod.rs | 109 -- web/src/api/system/api.ts | 4 +- web/src/features/dashboard/index.tsx | 1 - .../settings/profile/profile-form.tsx | 2 +- 171 files changed, 2203 insertions(+), 2042 deletions(-) create mode 100644 crates/admin/Cargo.toml rename src/bin/bichon_admin.rs => crates/admin/src/main.rs (97%) create mode 100644 crates/cli/Cargo.toml rename {src/modules/cli => crates/cli/src}/auth.rs (99%) rename {src/modules/cli => crates/cli/src}/eml/mod.rs (97%) rename src/bin/bichonctl.rs => crates/cli/src/main.rs (84%) rename {src/modules/cli => crates/cli/src}/mbox/gmail.rs (100%) rename {src/modules/cli => crates/cli/src}/mbox/mod.rs (96%) rename {src/modules/cli => crates/cli/src}/mbox/reader.rs (98%) rename {src/modules/cli => crates/cli/src}/pst/encoding/mod.rs (100%) rename {src/modules/cli => crates/cli/src}/pst/mod.rs (98%) rename {src/modules/cli => crates/cli/src}/sender.rs (96%) rename {src/modules/cli => crates/cli/src}/thunderbird/mod.rs (98%) create mode 100644 crates/core/Cargo.toml rename {src/modules => crates/core/src}/account/entity.rs (82%) rename {src/modules => crates/core/src}/account/grant.rs (98%) rename {src/modules => crates/core/src}/account/migration.rs (93%) rename {src/modules => crates/core/src}/account/mod.rs (100%) rename {src/modules => crates/core/src}/account/old_state.rs (96%) rename {src/modules => crates/core/src}/account/payload.rs (91%) rename {src/modules => crates/core/src}/account/since.rs (95%) rename {src/modules => crates/core/src}/account/state.rs (95%) rename {src/modules => crates/core/src}/account/view.rs (97%) rename {src/modules/cli => crates/core/src}/admin/meta.rs (99%) rename {src/modules/cli => crates/core/src}/admin/mod.rs (100%) rename {src/modules => crates/core/src}/autoconfig/entity.rs (90%) rename {src/modules => crates/core/src}/autoconfig/load.rs (96%) rename {src/modules => crates/core/src}/autoconfig/mod.rs (90%) rename {src/modules => crates/core/src}/autoconfig/tests.rs (100%) rename {src/modules => crates/core/src}/cache/imap/download/download_folders.rs (99%) rename {src/modules => crates/core/src}/cache/imap/download/download_type.rs (99%) rename {src/modules => crates/core/src}/cache/imap/download/flow.rs (99%) rename {src/modules => crates/core/src}/cache/imap/download/mod.rs (99%) rename {src/modules => crates/core/src}/cache/imap/download/rebuild.rs (99%) rename {src/modules => crates/core/src}/cache/imap/mailbox.rs (96%) rename {src/modules => crates/core/src}/cache/imap/mod.rs (86%) rename {src/modules => crates/core/src}/cache/imap/task.rs (94%) rename {src/modules => crates/core/src}/cache/mod.rs (96%) create mode 100644 crates/core/src/common/auth.rs rename {src/modules => crates/core/src}/common/mod.rs (60%) create mode 100644 crates/core/src/common/paginated.rs rename {src/modules => crates/core/src}/common/periodic.rs (98%) rename {src/modules => crates/core/src}/common/rustls.rs (98%) rename {src/modules => crates/core/src}/common/signal.rs (94%) rename {src/modules => crates/core/src}/context/controller.rs (97%) rename {src/modules => crates/core/src}/context/executors.rs (95%) rename {src/modules => crates/core/src}/context/mod.rs (91%) rename {src/modules => crates/core/src}/dashboard/mod.rs (95%) rename {src/modules => crates/core/src}/database/manager.rs (91%) rename {src/modules => crates/core/src}/database/mod.rs (94%) rename {src/modules => crates/core/src}/envelope/extractor.rs (96%) rename {src/modules => crates/core/src}/envelope/mod.rs (100%) rename {src/modules => crates/core/src}/envelope/utils.rs (98%) create mode 100644 crates/core/src/error/code.rs create mode 100644 crates/core/src/error/mod.rs rename {src/modules => crates/core/src}/imap/capabilities.rs (91%) rename {src/modules => crates/core/src}/imap/client.rs (95%) rename {src/modules => crates/core/src}/imap/executor.rs (95%) rename {src/modules => crates/core/src}/imap/manager.rs (92%) rename {src/modules => crates/core/src}/imap/mod.rs (100%) rename {src/modules => crates/core/src}/imap/oauth2.rs (100%) rename {src/modules => crates/core/src}/imap/session.rs (100%) rename {src/modules => crates/core/src}/imap/stats.rs (98%) rename {src/modules => crates/core/src}/imap/tests.rs (99%) rename {src/modules => crates/core/src}/import/mod.rs (95%) create mode 100644 crates/core/src/lib.rs rename {src/modules => crates/core/src}/logger/file.rs (94%) rename {src/modules => crates/core/src}/logger/mod.rs (96%) rename {src/modules => crates/core/src}/mailbox/delete.rs (98%) rename {src/modules => crates/core/src}/mailbox/list.rs (88%) rename {src/modules => crates/core/src}/mailbox/mod.rs (100%) rename {src/modules => crates/core/src}/message/append.rs (95%) rename {src/modules => crates/core/src}/message/attachment.rs (97%) rename {src/modules => crates/core/src}/message/contacts.rs (81%) rename {src/modules => crates/core/src}/message/content.rs (96%) rename {src/modules => crates/core/src}/message/delete.rs (86%) rename {src/modules => crates/core/src}/message/list.rs (85%) rename {src/modules => crates/core/src}/message/mod.rs (100%) rename {src/modules => crates/core/src}/message/search.rs (91%) rename {src/modules => crates/core/src}/message/tags.rs (80%) rename {src/modules => crates/core/src}/oauth2/entity.rs (95%) rename {src/modules => crates/core/src}/oauth2/flow.rs (97%) rename {src/modules => crates/core/src}/oauth2/mod.rs (100%) rename {src/modules => crates/core/src}/oauth2/pending.rs (99%) rename {src/modules => crates/core/src}/oauth2/refresh/mod.rs (94%) rename {src/modules => crates/core/src}/oauth2/task.rs (98%) rename {src/modules => crates/core/src}/oauth2/token.rs (97%) rename {src/modules => crates/core/src}/settings/cli.rs (99%) rename {src/modules => crates/core/src}/settings/dir.rs (96%) rename {src/modules => crates/core/src}/settings/io.rs (100%) rename {src/modules => crates/core/src}/settings/mod.rs (96%) rename {src/modules => crates/core/src}/settings/proxy.rs (97%) rename {src/modules => crates/core/src}/settings/system.rs (92%) rename {src/modules => crates/core/src}/store/envelope.rs (94%) rename {src/modules => crates/core/src}/store/mod.rs (100%) rename {src/modules => crates/core/src}/store/storage.rs (99%) rename {src/modules => crates/core/src}/store/tantivy/attachment.rs (99%) rename {src/modules => crates/core/src}/store/tantivy/envelope.rs (99%) rename {src/modules => crates/core/src}/store/tantivy/fields.rs (100%) rename {src/modules => crates/core/src}/store/tantivy/mod.rs (100%) rename {src/modules => crates/core/src}/store/tantivy/model.rs (99%) rename {src/modules => crates/core/src}/store/tantivy/schema.rs (99%) rename {src/modules => crates/core/src}/tasks/mod.rs (87%) rename {src/modules => crates/core/src}/token/mod.rs (94%) rename {src/modules => crates/core/src}/token/payload.rs (91%) rename {src/modules => crates/core/src}/token/view.rs (91%) rename {src/modules => crates/core/src}/users/acl.rs (86%) rename {src/modules => crates/core/src}/users/manager.rs (98%) rename {src/modules => crates/core/src}/users/minimal.rs (93%) rename {src/modules => crates/core/src}/users/mod.rs (99%) rename {src/modules => crates/core/src}/users/payload.rs (96%) rename {src/modules => crates/core/src}/users/permissions.rs (99%) rename {src/modules => crates/core/src}/users/role.rs (98%) rename {src/modules => crates/core/src}/users/view.rs (93%) rename {src/modules => crates/core/src}/utils/encrypt.rs (97%) rename {src/modules => crates/core/src}/utils/html.rs (100%) rename {src/modules => crates/core/src}/utils/mod.rs (93%) rename {src/modules => crates/core/src}/utils/net.rs (96%) rename {src/modules => crates/core/src}/utils/rate_limit/mod.rs (98%) rename {src/modules => crates/core/src}/utils/shutdown.rs (100%) rename {src/modules => crates/core/src}/utils/tls.rs (99%) rename {src/modules => crates/core/src}/version/mod.rs (89%) create mode 100644 crates/server/Cargo.toml rename build.rs => crates/server/build.rs (100%) create mode 100644 crates/server/src/common/auth.rs rename {src/modules => crates/server/src}/common/error.rs (96%) rename {src/modules => crates/server/src}/common/log.rs (100%) create mode 100644 crates/server/src/common/mod.rs rename {src/modules/context => crates/server/src/common}/status.rs (97%) rename {src/modules => crates/server/src}/common/timeout.rs (98%) rename {src/modules => crates/server/src}/common/tls.rs (98%) rename {src/modules => crates/server/src}/common/validator.rs (100%) rename {src/modules => crates/server/src}/error/code.rs (64%) rename {src/modules => crates/server/src}/error/handler.rs (95%) rename {src/modules => crates/server/src}/error/mod.rs (86%) rename {src => crates/server/src}/main.rs (87%) rename {src/modules => crates/server/src}/rest/api/access_token.rs (87%) rename {src/modules => crates/server/src}/rest/api/account.rs (90%) rename {src/modules => crates/server/src}/rest/api/attachment.rs (86%) rename {src/modules => crates/server/src}/rest/api/auto_config.rs (82%) rename {src/modules => crates/server/src}/rest/api/import.rs (85%) rename {src/modules => crates/server/src}/rest/api/mailbox.rs (87%) rename {src/modules => crates/server/src}/rest/api/message.rs (87%) rename {src/modules => crates/server/src}/rest/api/mod.rs (94%) rename {src/modules => crates/server/src}/rest/api/oauth2.rs (91%) rename {src/modules => crates/server/src}/rest/api/system.rs (82%) rename {src/modules => crates/server/src}/rest/api/users.rs (85%) rename {src/modules => crates/server/src}/rest/assets.rs (96%) rename {src/modules => crates/server/src}/rest/mod.rs (90%) rename {src/modules => crates/server/src}/rest/public/login.rs (98%) rename {src/modules => crates/server/src}/rest/public/mod.rs (100%) rename {src/modules => crates/server/src}/rest/public/oauth2.rs (97%) rename {src/modules => crates/server/src}/rest/public/status.rs (94%) create mode 100644 crates/smtp/Cargo.toml create mode 100644 crates/smtp/src/lib.rs rename {src/modules/smtp => crates/smtp/src}/server.rs (89%) rename {src/modules/smtp => crates/smtp/src}/stream.rs (100%) rename {src/modules/smtp => crates/smtp/src}/tests.rs (100%) rename {src/modules/smtp => crates/smtp/src}/tls.rs (98%) delete mode 100644 src/lib.rs delete mode 100644 src/modules/cli/mod.rs delete mode 100644 src/modules/common/auth.rs delete mode 100644 src/modules/common/paginated.rs delete mode 100644 src/modules/mod.rs delete mode 100644 src/modules/rest/response.rs delete mode 100644 src/modules/smtp/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7621423..ab5f548 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -166,15 +166,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] @@ -203,7 +203,7 @@ dependencies = [ "nom 7.1.3", "num-traits", "rusticata-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "time 0.3.47", ] @@ -255,26 +255,25 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.34" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -338,9 +337,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -420,9 +419,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -431,9 +430,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -460,10 +459,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "bichon" +name = "bichon-admin" +version = "1.0.0" +dependencies = [ + "bichon-core", + "console", + "dialoguer", + "tokio", +] + +[[package]] +name = "bichon-cli" +version = "1.0.0" +dependencies = [ + "base64 0.22.1", + "bichon-core", + "chrono", + "clap", + "codepage-strings", + "compressed-rtf", + "console", + "dialoguer", + "hex", + "mail-parser", + "mail-send", + "memmap2", + "outlook-pst", + "reqwest", + "serde", + "tokio", + "toml", +] + +[[package]] +name = "bichon-core" version = "1.0.0" dependencies = [ - "ahash", "async-imap", "autoconfig", "base64 0.22.1", @@ -471,75 +502,95 @@ dependencies = [ "bytes 1.11.1", "chrono", "clap", - "codepage-strings", - "compressed-rtf", - "console", "dashmap", - "dialoguer", "email_address", "encoding_rs", "fjall", "futures", - "gethostname", "governor", - "hex", "html2text", - "http 1.4.0", - "imap-proto", "itertools", "itoa", - "lettre", "lru 0.17.0", "mail-parser", "mail-send", - "memmap2", - "mimalloc", - "mime_guess", "murmur3", "native_db", "native_model", "num_cpus", "oauth2", - "openssl-sys", - "outlook-pst", - "poem", - "poem-derive", "poem-openapi", "rand 0.10.1", - "rcgen", "regex", "reqwest", "ring", - "rust-embed", "rustls", - "rustls-pemfile", "rustls-pki-types", - "semver 1.0.28", "serde", "serde_json", "snafu", "sysinfo", "tantivy", - "tempfile", "time 0.3.47", - "timeago", "tokio", "tokio-io-timeout", "tokio-rustls", "tokio-socks", "tokio-util", - "toml", "tracing", "tracing-appender", "tracing-log", "tracing-subscriber", - "url", - "urlencoding", "utf7-imap", "uuid", "webpki-roots", ] +[[package]] +name = "bichon-server" +version = "1.0.0" +dependencies = [ + "bichon-core", + "bichon-smtp", + "chrono", + "email_address", + "governor", + "http 1.4.0", + "mimalloc", + "poem", + "poem-derive", + "poem-openapi", + "rust-embed", + "serde", + "serde_json", + "snafu", + "tantivy", + "timeago", + "tokio", + "tracing", + "urlencoding", +] + +[[package]] +name = "bichon-smtp" +version = "1.0.0" +dependencies = [ + "base64 0.22.1", + "bichon-core", + "lettre", + "rcgen", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "tempfile", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + [[package]] name = "bincode" version = "1.3.3" @@ -551,9 +602,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitpacking" @@ -625,7 +676,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -657,9 +708,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecount" @@ -699,9 +750,9 @@ checksum = "1c53ba0f290bfc610084c05582d9c5d421662128fc69f4bf236707af6fd321b9" [[package]] name = "camino" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -730,9 +781,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.47" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -766,7 +817,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -828,15 +879,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -863,9 +914,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -890,14 +941,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b7f2aaf6791c6517669240b82ef12c1283b39772f0cad3bc5c73f19f6e7f16" dependencies = [ "byteorder", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "compression-codecs" -version = "0.4.33" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "brotli", "compression-core", @@ -936,9 +987,9 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8a2ca5ac02d09563609681103aada9e1777d54fc57a5acd7a41404f9c93b6e" +checksum = "413d67b29ef1021b4d60f4aa1e925ca031751e213832b4b1d588fae623c05c60" [[package]] name = "constant_time_eq" @@ -946,6 +997,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.14.4" @@ -957,7 +1017,7 @@ dependencies = [ "hkdf", "hmac", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "sha2 0.9.9", "time 0.2.27", "version_check", @@ -1108,15 +1168,15 @@ dependencies = [ "openssl-probe 0.1.6", "openssl-sys", "schannel", - "socket2 0.6.3", + "socket2", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.84+curl-8.17.0" +version = "0.4.87+curl-8.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc4294dc41b882eaff37973c2ec3ae203d0091341ee68fbadd1d06e0c18a73b" +checksum = "61a460380f0ef783703dcbe909107f39c162adeac050d73c850055118b5b6327" dependencies = [ "cc", "libc", @@ -1134,8 +1194,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -1152,13 +1222,37 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", "quote", "syn 2.0.117", ] @@ -1179,9 +1273,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "datasketches" @@ -1205,9 +1299,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -1215,21 +1309,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.1", "syn 2.0.117", "unicode-xid", ] @@ -1450,15 +1546,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fjall" @@ -1479,9 +1575,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1634,7 +1730,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-core", "futures-io", "parking", @@ -1720,9 +1816,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1740,21 +1836,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", - "rand_core 0.10.0", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1804,7 +1900,7 @@ dependencies = [ "parking_lot", "portable-atomic", "quanta", - "rand 0.9.2", + "rand 0.9.4", "smallvec", "spinning_top 0.3.0", "web-time", @@ -1812,9 +1908,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes 1.11.1", @@ -1868,10 +1964,11 @@ dependencies = [ [[package]] name = "hashify" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "149e3ea90eb5a26ad354cfe3cb7f7401b9329032d0235f2687d03a35f30e5d4c" +checksum = "dd1246c0e5493286aeb2dde35b1f4eb9c4ce00e628641210a5e553fc001a1f26" dependencies = [ + "indexmap", "proc-macro2", "quote", "syn 2.0.117", @@ -1944,11 +2041,11 @@ dependencies = [ "idna 1.1.0", "ipnet", "once_cell", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustls", "rustls-pki-types", - "thiserror 2.0.17", + "thiserror 2.0.18", "time 0.3.47", "tinyvec", "tokio", @@ -1970,11 +2067,11 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "resolv-conf", "rustls", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-rustls", "tracing", @@ -2019,7 +2116,7 @@ checksum = "d1c97eea9d3e6524d8d2a4643ce0e3135500c4c7ca1d02393a485f871bef69d8" dependencies = [ "html5ever", "tendril", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-width", ] @@ -2133,9 +2230,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes 1.11.1", @@ -2148,7 +2245,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2156,15 +2252,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -2173,14 +2268,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes 1.11.1", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body", @@ -2189,7 +2283,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", "tokio", "tower-service", "tracing", @@ -2197,9 +2291,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2221,12 +2315,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2234,9 +2329,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2247,9 +2342,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2261,15 +2356,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2281,15 +2376,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2346,21 +2441,21 @@ dependencies = [ [[package]] name = "imap-proto" -version = "0.16.6" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1f9b30846c3d04371159ef3a0413ce7c1ae0a8c619cd255c60b3d902553f22" +checksum = "25f6af35c6a517aea5c72314abe90134980d2ae6a763809b50c208b3e429d71f" dependencies = [ "nom 7.1.3", ] [[package]] name = "indexmap" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2400,27 +2495,28 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -2491,7 +2587,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", "windows-link", ] @@ -2540,10 +2636,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2578,7 +2676,7 @@ dependencies = [ "base64 0.22.1", "email-encoding", "email_address", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-util", "hostname", "httpdate", @@ -2588,7 +2686,7 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "socket2 0.6.3", + "socket2", "tokio", "url", ] @@ -2601,9 +2699,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libmimalloc-sys" @@ -2616,9 +2714,9 @@ dependencies = [ [[package]] name = "libnghttp2-sys" -version = "0.1.11+1.64.0" +version = "0.1.13+1.68.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6c24e48a7167cffa7119da39d577fa482e66c688a4aac016bee862e1a713c4" +checksum = "492e00167f1418c15648144f42bbfc63099806ecee9bf8d09a6353d6b4856b3c" dependencies = [ "cc", "libc", @@ -2626,9 +2724,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -2650,9 +2748,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2665,18 +2763,18 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ "value-bag", ] [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -2787,7 +2885,7 @@ dependencies = [ "mail-auth", "mail-builder", "md5", - "rand 0.9.2", + "rand 0.9.4", "rustls", "rustls-pki-types", "rustls-platform-verifier", @@ -2839,9 +2937,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -2906,9 +3004,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.12" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -3066,9 +3164,9 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -3094,9 +3192,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -3143,9 +3241,9 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64 0.22.1", "chrono", - "getrandom 0.2.16", + "getrandom 0.2.17", "http 1.4.0", - "rand 0.8.5", + "rand 0.8.6", "reqwest", "serde", "serde_json", @@ -3195,9 +3293,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -3223,9 +3321,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags", "cfg-if", @@ -3259,15 +3357,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-src" -version = "300.5.4+3.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.114" @@ -3276,7 +3365,6 @@ checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -3296,7 +3384,7 @@ version = "1.1.0" source = "git+https://github.com/rustmailer/outlook-pst-rs.git?branch=main#3d36200fa359161a127287f47ca031ea8b802e48" dependencies = [ "byteorder", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -3389,7 +3477,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "phf_shared 0.13.1", ] @@ -3413,18 +3501,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -3433,9 +3521,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -3445,20 +3533,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-io", ] [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "poem" @@ -3495,7 +3583,7 @@ dependencies = [ "smallvec", "sync_wrapper", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-rustls", "tokio-stream", @@ -3539,7 +3627,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "serde_yaml", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", ] @@ -3549,7 +3637,7 @@ version = "5.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41273b691a3d467a8c44d05506afba9f7b6bd56c9cdf80123de13fe52d7ec587" dependencies = [ - "darling", + "darling 0.20.11", "http 1.4.0", "indexmap", "mime", @@ -3558,7 +3646,7 @@ dependencies = [ "quote", "regex", "syn 2.0.117", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3588,15 +3676,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -3634,9 +3722,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -3703,9 +3791,9 @@ dependencies = [ [[package]] name = "quick_cache" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" +checksum = "5a70b1b8b47e31d0498ecbc3c5470bb931399a8bfed1fd79d1717a61ce7f96e3" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -3724,8 +3812,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", - "thiserror 2.0.17", + "socket2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3733,20 +3821,20 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes 1.11.1", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3761,7 +3849,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -3777,9 +3865,9 @@ dependencies = [ [[package]] name = "quoted_printable" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" [[package]] name = "r-efi" @@ -3787,6 +3875,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.7.3" @@ -3802,9 +3896,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3813,12 +3907,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3828,8 +3922,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.1", - "rand_core 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -3859,7 +3953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3877,23 +3971,23 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_hc" @@ -3915,9 +4009,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -3988,9 +4082,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3999,15 +4093,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes 1.11.1", @@ -4068,7 +4162,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted 0.9.0", "windows-sys 0.52.0", @@ -4087,9 +4181,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.9.0" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" dependencies = [ "proc-macro2", "quote", @@ -4100,9 +4194,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.9.0" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ "sha2 0.10.9", "walkdir", @@ -4120,9 +4214,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -4240,9 +4334,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -4258,9 +4352,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -4273,9 +4367,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -4288,9 +4382,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation", @@ -4311,9 +4405,9 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" [[package]] name = "semver" @@ -4419,9 +4513,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -4535,18 +4629,19 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" @@ -4566,9 +4661,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "skeptic" @@ -4596,9 +4691,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "sluice" @@ -4619,9 +4714,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smtp-proto" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55cc1c74d3b758d7dd1fa4dc4cf694cad2732cac14f304228477c2b0ce6233a" +checksum = "97c9f5dd7ec5cc6d743f33fcb96de4eb91bb1cc51c5e0ba40cb285a9012043da" [[package]] name = "snafu" @@ -4644,16 +4739,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -4814,7 +4899,7 @@ dependencies = [ "cfg-if", "encoding_rs", "futures-util", - "getrandom 0.2.16", + "getrandom 0.2.17", "http-client", "http-types", "log", @@ -4826,6 +4911,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -4890,9 +4981,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tantivy" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778da245841522199d512d19511b041425d8cff3a8f262b4e1516fceb050289a" +checksum = "edde6a10743fff00a4e1a8c9ef020bf5f3cbad301b7d2d39f2b07f123c4eac07" dependencies = [ "aho-corasick", "arc-swap", @@ -4912,7 +5003,7 @@ dependencies = [ "itertools", "levenshtein_automata", "log", - "lru 0.16.3", + "lru 0.16.4", "lz4_flex", "measure_time", "memmap2", @@ -4934,7 +5025,7 @@ dependencies = [ "tantivy-stacker", "tantivy-tokenizer-api", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time 0.3.47", "typetag", "uuid", @@ -5043,8 +5134,8 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.3.0", - "getrandom 0.4.1", + "fastrand 2.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -5071,11 +5162,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -5091,9 +5182,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -5192,9 +5283,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -5202,9 +5293,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -5227,7 +5318,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -5277,9 +5368,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -5301,17 +5392,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -5324,37 +5415,46 @@ dependencies = [ ] [[package]] -name = "toml_edit" -version = "0.23.7" +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.2", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.2", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -5367,9 +5467,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes 1.11.1", @@ -5409,12 +5509,13 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "symlink", + "thiserror 2.0.18", "time 0.3.47", "tracing-subscriber", ] @@ -5508,7 +5609,7 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "thiserror 1.0.69", "tinyvec", @@ -5563,9 +5664,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "typetag" @@ -5602,9 +5703,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -5614,9 +5715,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -5627,6 +5728,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + [[package]] name = "unicode-width" version = "0.2.2" @@ -5727,7 +5834,7 @@ version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -5802,11 +5909,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen 0.46.0", + "wit-bindgen 0.57.1", ] [[package]] @@ -5820,9 +5927,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -5833,22 +5940,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5856,9 +5960,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -5869,9 +5973,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -5925,9 +6029,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5957,9 +6061,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -6099,6 +6203,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -6117,15 +6232,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -6162,21 +6268,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -6219,12 +6310,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6237,12 +6322,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6255,12 +6334,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6285,12 +6358,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6303,12 +6370,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6321,12 +6382,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6339,12 +6394,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6359,29 +6408,19 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] -[[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" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -6391,6 +6430,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -6472,9 +6517,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x509-parser" @@ -6490,7 +6535,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "time 0.3.47", ] @@ -6517,9 +6562,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -6528,9 +6573,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -6540,18 +6585,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -6560,18 +6605,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -6587,9 +6632,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -6598,9 +6643,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -6609,9 +6654,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -6633,9 +6678,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.5" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3280a1b827474fcd5dbef4b35a674deb52ba5c312363aef9135317df179d81b" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 9175525..967fc38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,48 +1,20 @@ -[package] -name = "bichon" +[workspace] + +members = ["crates/core", "crates/server", "crates/cli", "crates/admin"] + +resolver = "2" + +[workspace.package] version = "1.0.0" edition = "2021" -[[bin]] -name = "bichon" -path = "src/main.rs" - -[[bin]] -name = "bichonctl" -path = "src/bin/bichonctl.rs" - -[[bin]] -name = "bichon-admin" -path = "src/bin/bichon_admin.rs" - - -[features] -default = [] -vendored-openssl = ["openssl-sys"] - -[profile.release] -strip = true -lto = true -opt-level = 3 -codegen-units = 1 - -[dependencies] +[workspace.dependencies] chrono = "0.4.44" clap = { version = "4.6.1", features = ["derive", "env"] } mimalloc = "0.1.49" native_db = "0.8.2" itertools = "0.14.0" native_model = "0.4.20" -poem = { version = "3.1.12", features = ["embed", "compression", "rustls"] } -poem-derive = "3.1.12" -poem-openapi = { version = "5.1.16", features = [ - "openapi-explorer", - "rapidoc", - "scalar", - "redoc", - "swagger-ui", - "email", -] } ring = { version = "0.17.14", features = ["std"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" @@ -74,7 +46,6 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = [ "tls12", ] } timeago = "0.6.0" -ahash = "0.8.12" oauth2 = { version = "5.0.0", features = ["reqwest-blocking"] } url = { version = "2.5.8", features = ["serde"] } sysinfo = "0.38.4" @@ -104,19 +75,12 @@ murmur3 = "0.5.2" autoconfig = "0.4.0" urlencoding = "2.1.3" dashmap = "6.1.0" -# Statically links OpenSSL by compiling from source, avoiding system library dependencies -openssl-sys = { version = "0.9.114", optional = true, features = ["vendored"] } gethostname = "1.1.0" itoa = "1.0.18" html2text = "0.17.1" bytes = "1.11.1" dialoguer = "0.12.0" console = "0.16.3" -toml = "0.9.8" -memmap2 = "0.9.10" -outlook-pst = { git = "https://github.com/rustmailer/outlook-pst-rs.git", branch = "main" } -compressed-rtf = "1.0.1" -codepage-strings = "1.0.2" mail-send = "0.6.0" rcgen = "0.14.7" rustls-pemfile = "2.2.0" @@ -126,8 +90,10 @@ fjall = { version = "3.1.4", features = ["lz4", "metrics", "bytes_1"] } tantivy = { version = "0.26.0", features = ["zstd-compression"] } tracing-log = "0.2.0" tokio-util = "0.7.18" -[dev-dependencies] -#bincode = "1.3.3" -#secret-lib = "1.0.0" -tempfile = "3.27.0" -lettre = "0.11.21" + + +[profile.release] +strip = true +lto = true +opt-level = 3 +codegen-units = 1 diff --git a/crates/admin/Cargo.toml b/crates/admin/Cargo.toml new file mode 100644 index 0000000..f65744d --- /dev/null +++ b/crates/admin/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bichon-admin" +version.workspace = true +edition.workspace = true + + +[dependencies] +bichon-core = { path = "../core" } +tokio.workspace = true +dialoguer.workspace = true +console.workspace = true \ No newline at end of file diff --git a/src/bin/bichon_admin.rs b/crates/admin/src/main.rs similarity index 97% rename from src/bin/bichon_admin.rs rename to crates/admin/src/main.rs index 9b0404c..f94146b 100644 --- a/src/bin/bichon_admin.rs +++ b/crates/admin/src/main.rs @@ -16,22 +16,19 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use std::{ fs, path::{Path, PathBuf}, - rc::Rc, }; -use bichon::modules::{ - cli::admin::meta::{find_admin, init_meta_database, update_admin_password}, +use bichon_core::{ + admin::meta::{find_admin, init_meta_database, update_admin_password}, error::BichonError, utils::encrypt::internal_decrypt_string, }; use console::{style, Emoji}; use dialoguer::Confirm; use dialoguer::{theme::ColorfulTheme, Input, Password, Select}; -use native_db::Database; #[tokio::main] async fn main() { @@ -75,7 +72,7 @@ async fn main() { let root_path = PathBuf::from(&root_dir_str); - let database: Rc> = match init_meta_database(&root_path.join("meta.db")) { + let database = match init_meta_database(&root_path.join("meta.db")) { Ok(database) => database, Err(e) => match e { BichonError::Generic { diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 0000000..7d16cf4 --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bichon-cli" +version.workspace = true +edition.workspace = true + + +[dependencies] +bichon-core = { path = "../core" } +tokio.workspace = true +serde.workspace = true +clap.workspace = true +dialoguer.workspace = true +console.workspace = true +mail-parser.workspace = true +reqwest.workspace = true +toml = "0.9.8" +memmap2 = "0.9.10" +outlook-pst = { git = "https://github.com/rustmailer/outlook-pst-rs.git", branch = "main" } +compressed-rtf = "1.0.1" +chrono.workspace = true +mail-send.workspace = true +base64.workspace = true +codepage-strings = "1.0.2" +hex = "0.4.3" \ No newline at end of file diff --git a/src/modules/cli/auth.rs b/crates/cli/src/auth.rs similarity index 99% rename from src/modules/cli/auth.rs rename to crates/cli/src/auth.rs index 31d592d..3db1afb 100644 --- a/src/modules/cli/auth.rs +++ b/crates/cli/src/auth.rs @@ -16,19 +16,19 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use std::process; use console::style; use dialoguer::{theme::ColorfulTheme, Select}; use reqwest::Client; -use crate::modules::{ +use bichon_core::{ account::payload::MinimalAccount, - cli::BichonCtlConfig, users::{permissions::Permission, view::UserView}, }; +use crate::BichonCtlConfig; + pub async fn verify_user_and_get_account(config: &BichonCtlConfig, theme: &ColorfulTheme) -> u64 { let client = Client::new(); let url = format!("{}/api/v1/current-user", config.base_url); diff --git a/src/modules/cli/eml/mod.rs b/crates/cli/src/eml/mod.rs similarity index 97% rename from src/modules/cli/eml/mod.rs rename to crates/cli/src/eml/mod.rs index 41a2d68..78667bc 100644 --- a/src/modules/cli/eml/mod.rs +++ b/crates/cli/src/eml/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use std::{ collections::HashMap, fs, @@ -28,10 +27,9 @@ use dialoguer::{theme::ColorfulTheme, Input}; use mail_parser::MessageParser; use reqwest::Client; -use crate::{ - base64_encode_url_safe, - modules::cli::{sender::send_batch_request, BichonCtlConfig}, -}; +use bichon_core::base64_encode_url_safe; + +use crate::{sender::send_batch_request, BichonCtlConfig}; pub async fn handle_eml_directory_import( config: &BichonCtlConfig, diff --git a/src/bin/bichonctl.rs b/crates/cli/src/main.rs similarity index 84% rename from src/bin/bichonctl.rs rename to crates/cli/src/main.rs index c9379c9..a558e34 100644 --- a/src/bin/bichonctl.rs +++ b/crates/cli/src/main.rs @@ -16,17 +16,51 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - -use bichon::modules::cli::{ - auth::verify_user_and_get_account, eml::handle_eml_directory_import, - mbox::handle_mbox_single_file_import, pst::handle_pst_import, - thunderbird::handle_thunderbird_import, BichonCli, BichonCtlConfig, -}; +use bichon_core::bichon_version; use clap::Parser; use console::style; use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; +use serde::{Deserialize, Serialize}; use std::fs; +use crate::{ + auth::verify_user_and_get_account, eml::handle_eml_directory_import, + mbox::handle_mbox_single_file_import, pst::handle_pst_import, + thunderbird::handle_thunderbird_import, +}; + +pub mod auth; +pub mod eml; +pub mod mbox; +pub mod pst; +pub mod sender; +pub mod thunderbird; + +#[derive(Parser, Debug)] +#[command( + name = "bichonctl", + author = "rustmailer", + version = bichon_version!(), + about = "A CLI tool to import email data into Bichon service" +)] +pub struct BichonCli { + /// Path to the configuration file + #[arg( + short, + long, + default_value = "config.toml", + value_name = "FILE", + help = "Sets a custom config file" + )] + pub config: std::path::PathBuf, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct BichonCtlConfig { + pub base_url: String, + pub api_token: String, +} + #[tokio::main] async fn main() { let cli = BichonCli::parse(); diff --git a/src/modules/cli/mbox/gmail.rs b/crates/cli/src/mbox/gmail.rs similarity index 100% rename from src/modules/cli/mbox/gmail.rs rename to crates/cli/src/mbox/gmail.rs diff --git a/src/modules/cli/mbox/mod.rs b/crates/cli/src/mbox/mod.rs similarity index 96% rename from src/modules/cli/mbox/mod.rs rename to crates/cli/src/mbox/mod.rs index 7a6a401..a062a1b 100644 --- a/src/modules/cli/mbox/mod.rs +++ b/crates/cli/src/mbox/mod.rs @@ -19,11 +19,11 @@ use std::collections::HashMap; use std::path::PathBuf; -use crate::base64_encode_url_safe; -use crate::modules::cli::mbox::gmail::determine_folder; -use crate::modules::cli::mbox::reader::MboxFile; -use crate::modules::cli::sender::send_batch_request; -use crate::modules::cli::BichonCtlConfig; +use crate::mbox::gmail::determine_folder; +use crate::mbox::reader::MboxFile; +use crate::sender::send_batch_request; +use crate::BichonCtlConfig; +use bichon_core::base64_encode_url_safe; use console::style; use dialoguer::{theme::ColorfulTheme, Input}; use dialoguer::{Confirm, Select}; diff --git a/src/modules/cli/mbox/reader.rs b/crates/cli/src/mbox/reader.rs similarity index 98% rename from src/modules/cli/mbox/reader.rs rename to crates/cli/src/mbox/reader.rs index 6146281..b5e2730 100644 --- a/src/modules/cli/mbox/reader.rs +++ b/crates/cli/src/mbox/reader.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use memmap2::Mmap; use std::fs; use std::io; @@ -130,7 +129,7 @@ impl<'a> Iterator for MboxReader<'a> { mod tests { use mail_parser::MessageParser; - use crate::modules::cli::mbox::gmail::determine_folder; + use crate::mbox::gmail::determine_folder; use super::*; @@ -221,7 +220,6 @@ mod tests { labels, determine_folder(labels) ) - } } } diff --git a/src/modules/cli/pst/encoding/mod.rs b/crates/cli/src/pst/encoding/mod.rs similarity index 100% rename from src/modules/cli/pst/encoding/mod.rs rename to crates/cli/src/pst/encoding/mod.rs diff --git a/src/modules/cli/pst/mod.rs b/crates/cli/src/pst/mod.rs similarity index 98% rename from src/modules/cli/pst/mod.rs rename to crates/cli/src/pst/mod.rs index 060b596..a0b5917 100644 --- a/src/modules/cli/pst/mod.rs +++ b/crates/cli/src/pst/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use chrono::{DateTime, TimeZone, Utc}; use dialoguer::theme::ColorfulTheme; use dialoguer::Input; @@ -24,10 +23,10 @@ use mail_send::mail_builder::headers::text::Text; use mail_send::mail_builder::MessageBuilder; use outlook_pst::ltp::prop_context::PropertyValue; -use crate::base64_encode_url_safe; -use crate::modules::cli::pst::encoding::decode_subject; -use crate::modules::cli::sender::send_batch_request; -use crate::modules::cli::BichonCtlConfig; +use crate::BichonCtlConfig; +use crate::pst::encoding::decode_subject; +use crate::sender::send_batch_request; +use bichon_core::base64_encode_url_safe; use dialoguer::Confirm; use outlook_pst::messaging::attachment::AttachmentProperties; use outlook_pst::messaging::folder::Folder; diff --git a/src/modules/cli/sender.rs b/crates/cli/src/sender.rs similarity index 96% rename from src/modules/cli/sender.rs rename to crates/cli/src/sender.rs index 4294bda..761631f 100644 --- a/src/modules/cli/sender.rs +++ b/crates/cli/src/sender.rs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use console::style; use reqwest::Client; -use crate::modules::{cli::BichonCtlConfig, import::BatchEmlRequest}; +use bichon_core::import::BatchEmlRequest; + +use crate::BichonCtlConfig; pub async fn send_batch_request( client: &Client, diff --git a/src/modules/cli/thunderbird/mod.rs b/crates/cli/src/thunderbird/mod.rs similarity index 98% rename from src/modules/cli/thunderbird/mod.rs rename to crates/cli/src/thunderbird/mod.rs index 2cc97fd..c82d725 100644 --- a/src/modules/cli/thunderbird/mod.rs +++ b/crates/cli/src/thunderbird/mod.rs @@ -16,10 +16,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use std::{collections::HashMap, path::PathBuf}; -use crate::modules::cli::{mbox::run_import, BichonCtlConfig}; +use crate::{mbox::run_import, BichonCtlConfig}; use console::style; use dialoguer::{theme::ColorfulTheme, Confirm, Input}; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..64413f9 --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "bichon-core" +version.workspace = true +edition.workspace = true + + +[features] +default = ["web-api"] +web-api = ["dep:poem-openapi"] + +[dependencies] +poem-openapi = { version = "5.1.16", features = [ + "openapi-explorer", + "rapidoc", + "scalar", + "redoc", + "swagger-ui", + "email", +], optional = true } +chrono.workspace = true +clap.workspace = true +native_db.workspace = true +itertools.workspace = true +native_model.workspace = true +ring.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +tracing.workspace = true +tracing-appender.workspace = true +tracing-subscriber.workspace = true +base64.workspace = true +snafu.workspace = true +reqwest.workspace = true +tokio-socks.workspace = true +regex.workspace = true +email_address.workspace = true +futures.workspace = true +utf7-imap.workspace = true +mail-parser.workspace = true +tokio-rustls.workspace = true +oauth2.workspace = true +sysinfo.workspace = true +num_cpus.workspace = true +rand.workspace = true +encoding_rs.workspace = true +async-imap.workspace = true +webpki-roots.workspace = true +rustls.workspace = true +rustls-pki-types.workspace = true +tokio-io-timeout.workspace = true +governor.workspace = true +lru.workspace = true +time.workspace = true +murmur3.workspace = true +autoconfig.workspace = true +dashmap.workspace = true +itoa.workspace = true +html2text.workspace = true +bytes.workspace = true +mail-send.workspace = true +blake3.workspace = true +uuid.workspace = true +fjall.workspace = true +tantivy.workspace = true +tracing-log.workspace = true +tokio-util.workspace = true diff --git a/src/modules/account/entity.rs b/crates/core/src/account/entity.rs similarity index 82% rename from src/modules/account/entity.rs rename to crates/core/src/account/entity.rs index fa206ef..f29d166 100644 --- a/src/modules/account/entity.rs +++ b/crates/core/src/account/entity.rs @@ -16,19 +16,19 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use crate::{encrypt, error::BichonResult}; -use crate::{encrypt, modules::error::BichonResult}; - -use poem_openapi::{Enum, Object}; +//use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct ImapConfig { /// IMAP server hostname or IP address - #[oai(validator(max_length = 253, pattern = r"^[a-zA-Z0-9\-\.]+$"))] + //#[oai(validator(max_length = 253, pattern = r"^[a-zA-Z0-9\-\.]+$"))] pub host: String, /// IMAP server port number - #[oai(validator(minimum(value = "1"), maximum(value = "65535")))] + //#[oai(validator(minimum(value = "1"), maximum(value = "65535")))] pub port: u16, /// Connection encryption method pub encryption: Encryption, @@ -52,8 +52,8 @@ impl ImapConfig { } } - -#[derive(Enum, Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum AuthType { /// Standard password authentication (PLAIN/LOGIN) #[default] @@ -62,7 +62,8 @@ pub enum AuthType { OAuth2, } -#[derive(Object, Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AuthConfig { ///Authentication method to use pub auth_type: AuthType, @@ -70,7 +71,7 @@ pub struct AuthConfig { /// /// Users should provide a plaintext password (1 to 256 characters). /// The server will encrypt the password using AES-256-GCM and securely store it. - #[oai(validator(max_length = 256, min_length = 1))] + //#[oai(validator(max_length = 256, min_length = 1))] pub password: Option, } @@ -97,7 +98,8 @@ impl AuthConfig { } } -#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Enum)] +#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum Encryption { /// SSL/TLS encrypted connection #[default] diff --git a/src/modules/account/grant.rs b/crates/core/src/account/grant.rs similarity index 98% rename from src/modules/account/grant.rs rename to crates/core/src/account/grant.rs index d0b61e3..d7ad697 100644 --- a/src/modules/account/grant.rs +++ b/crates/core/src/account/grant.rs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use crate::{ - modules::{ + raise_error, utc_now, + { account::migration::AccountModel, common::auth::ClientContext, database::{manager::DB_MANAGER, with_transaction}, @@ -31,10 +32,10 @@ use crate::{ UserModel, }, }, - raise_error, utc_now, }; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct BatchAccountRoleRequest { pub account_ids: Vec, pub user_ids: Vec, diff --git a/src/modules/account/migration.rs b/crates/core/src/account/migration.rs similarity index 93% rename from src/modules/account/migration.rs rename to crates/core/src/account/migration.rs index ea0ba41..b276ea3 100644 --- a/src/modules/account/migration.rs +++ b/crates/core/src/account/migration.rs @@ -18,56 +18,54 @@ use native_db::*; use native_model::{native_model, Model}; - -use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use tracing::info; use crate::{ - encrypt, - modules::{ - account::{ - entity::ImapConfig, - since::{DateSince, RelativeDate}, - state::DownloadState, - }, - cache::imap::mailbox::MailBox, - database::{list_all_impl, secondary_find_impl, with_transaction}, - error::BichonResult, - store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER}, - users::{role::DEFAULT_ACCOUNT_MANAGER_ROLE_ID, UserModel, DEFAULT_ADMIN_USER_ID}, + account::{ + entity::ImapConfig, + since::{DateSince, RelativeDate}, + state::DownloadState, }, + cache::imap::mailbox::MailBox, + common::paginated::DataPage, + database::{list_all_impl, secondary_find_impl, with_transaction}, + encrypt, + error::BichonResult, + store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER}, + users::{role::DEFAULT_ACCOUNT_MANAGER_ROLE_ID, UserModel, DEFAULT_ADMIN_USER_ID}, utc_now, }; -use crate::id; -use crate::modules::account::payload::AccountCreateRequest; -use crate::modules::account::payload::AccountUpdateRequest; -use crate::modules::account::payload::MinimalAccount; -use crate::modules::cache::imap::task::SYNC_TASKS; -use crate::modules::context::controller::DOWNLOAD_CONTROLLER; -use crate::modules::database::count_by_unique_secondary_key_impl; -use crate::modules::database::delete_impl; -use crate::modules::database::manager::DB_MANAGER; -use crate::modules::database::{ +use crate::account::payload::AccountCreateRequest; +use crate::account::payload::AccountUpdateRequest; +use crate::account::payload::MinimalAccount; +use crate::cache::imap::task::SYNC_TASKS; +use crate::context::controller::DOWNLOAD_CONTROLLER; +use crate::database::count_by_unique_secondary_key_impl; +use crate::database::delete_impl; +use crate::database::manager::DB_MANAGER; +use crate::database::{ async_secondary_find_impl, paginate_query_primary_scan_all_impl, update_impl, }; -use crate::modules::error::code::ErrorCode; -use crate::modules::oauth2::token::OAuth2AccessToken; -use crate::modules::rest::response::DataPage; +use crate::error::code::ErrorCode; +use crate::id; +use crate::oauth2::token::OAuth2AccessToken; use crate::raise_error; pub type AccountModel = AccountV4; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum AccountType { #[default] IMAP, NoSync, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum QuotaWindow { Hourly, #[default] @@ -76,7 +74,7 @@ pub enum QuotaWindow { Monthly, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[native_model(id = 4, version = 1)] #[native_db(primary_key(pk -> String))] pub struct AccountV1 { @@ -84,7 +82,7 @@ pub struct AccountV1 { pub id: u64, pub imap: Option, pub enabled: bool, - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: String, pub name: Option, pub capabilities: Option>, @@ -104,7 +102,7 @@ impl AccountV1 { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[native_model(id = 4, version = 2, from = AccountV1)] #[native_db(primary_key(pk -> String))] pub struct AccountV2 { @@ -112,7 +110,7 @@ pub struct AccountV2 { pub id: u64, pub imap: Option, pub enabled: bool, - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: String, pub name: Option, pub capabilities: Option>, @@ -135,7 +133,7 @@ impl AccountV2 { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[native_model(id = 4, version = 3, from = AccountV2)] #[native_db(primary_key(pk -> String))] pub struct AccountV3 { @@ -143,7 +141,7 @@ pub struct AccountV3 { pub id: u64, pub imap: Option, pub enabled: bool, - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: String, pub name: Option, pub capabilities: Option>, @@ -169,7 +167,8 @@ impl AccountV3 { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 4, version = 4, from = AccountV3)] #[native_db(primary_key(pk -> String))] pub struct AccountV4 { @@ -177,7 +176,7 @@ pub struct AccountV4 { pub id: u64, pub imap: Option, pub enabled: bool, - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: String, pub account_name: Option, pub login_name: Option, diff --git a/src/modules/account/mod.rs b/crates/core/src/account/mod.rs similarity index 100% rename from src/modules/account/mod.rs rename to crates/core/src/account/mod.rs diff --git a/src/modules/account/old_state.rs b/crates/core/src/account/old_state.rs similarity index 96% rename from src/modules/account/old_state.rs rename to crates/core/src/account/old_state.rs index fa63f76..9bd5c13 100644 --- a/src/modules/account/old_state.rs +++ b/crates/core/src/account/old_state.rs @@ -16,26 +16,18 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -// use crate::{ -// modules::{ -// database::{async_find_impl, delete_impl, manager::DB_MANAGER, update_impl, upsert_impl}, -// error::{code::ErrorCode, BichonResult}, -// }, -// raise_error, utc_now, -// }; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::Object; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct MailboxBatchProgress { pub total_batches: u32, pub current_batch: u32, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[native_model(id = 2, version = 1)] #[native_db] pub struct AccountRunningState { @@ -51,7 +43,7 @@ pub struct AccountRunningState { pub initial_sync_failed_time: Option, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct AccountError { pub error: String, pub at: i64, diff --git a/src/modules/account/payload.rs b/crates/core/src/account/payload.rs similarity index 91% rename from src/modules/account/payload.rs rename to crates/core/src/account/payload.rs index b4ad25c..be1c5ed 100644 --- a/src/modules/account/payload.rs +++ b/crates/core/src/account/payload.rs @@ -16,18 +16,18 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::entity::ImapConfig; -use crate::modules::account::migration::{AccountModel, AccountType, QuotaWindow}; -use crate::modules::account::since::{DateSince, RelativeDate}; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; +use crate::account::entity::ImapConfig; +use crate::account::migration::{AccountModel, AccountType, QuotaWindow}; +use crate::account::since::{DateSince, RelativeDate}; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; use crate::{raise_error, validate_email}; -use poem_openapi::Object; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccountCreateRequest { - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: String, pub login_name: Option, pub account_name: Option, @@ -36,11 +36,11 @@ pub struct AccountCreateRequest { pub date_since: Option, pub date_before: Option, pub account_type: AccountType, - #[oai(validator(minimum(value = "100")))] + //#[oai(validator(minimum(value = "100")))] pub folder_limit: Option, - #[oai(validator(minimum(value = "10")))] + //#[oai(validator(minimum(value = "10")))] pub download_interval_min: Option, - #[oai(validator(minimum(value = "10"), maximum(value = "200")))] + //#[oai(validator(minimum(value = "10"), maximum(value = "200")))] pub download_batch_size: Option, pub use_proxy: Option, pub use_dangerous: bool, @@ -107,7 +107,8 @@ impl AccountCreateRequest { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccountUpdateRequest { pub email: Option, /// Represents the account activation status. @@ -135,7 +136,7 @@ pub struct AccountUpdateRequest { /// Max emails to sync for this folder. /// If not set, sync all emails. /// otherwise sync up to `n` most recent emails (min 10). - #[oai(validator(minimum(value = "100")))] + //#[oai(validator(minimum(value = "100")))] pub folder_limit: Option, pub clear_folder_limit: Option, /// Configuration for selective folder (mailbox/label) synchronization @@ -153,9 +154,9 @@ pub struct AccountUpdateRequest { /// Modified folders will be automatically synced on the next update. pub sync_folders: Option>, /// Incremental sync interval (seconds) - #[oai(validator(minimum(value = "10")))] + //#[oai(validator(minimum(value = "10")))] pub download_interval_min: Option, - #[oai(validator(minimum(value = "10"), maximum(value = "200")))] + //#[oai(validator(minimum(value = "10"), maximum(value = "200")))] pub download_batch_size: Option, /// Optional proxy ID for establishing the connection to external APIs (e.g., Gmail, Outlook). /// - If `None` or not provided, the client will connect directly to the API server. @@ -225,7 +226,8 @@ impl AccountUpdateRequest { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct MinimalAccount { pub id: u64, diff --git a/src/modules/account/since.rs b/crates/core/src/account/since.rs similarity index 95% rename from src/modules/account/since.rs rename to crates/core/src/account/since.rs index 9ed5e81..a41366f 100644 --- a/src/modules/account/since.rs +++ b/crates/core/src/account/since.rs @@ -17,14 +17,14 @@ // along with this program. If not, see . use crate::{ - modules::error::{code::ErrorCode, BichonResult}, + error::{code::ErrorCode, BichonResult}, raise_error, }; use chrono::{Datelike, Days, Local, Months, NaiveDate, Utc}; -use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct DateSince { /// Absolute date boundary in ISO 8601 format (YYYY-MM-DD) /// @@ -38,7 +38,7 @@ pub struct DateSince { /// "fixed": "2025-05-01" /// } /// ``` - #[oai(validator(pattern = r"^\d{4}-\d{2}-\d{2}$"))] + //#[oai(validator(pattern = r"^\d{4}-\d{2}-\d{2}$"))] pub fixed: Option, /// Relative time period from current date /// @@ -58,7 +58,8 @@ pub struct DateSince { pub relative: Option, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum Unit { #[default] Days, @@ -66,12 +67,13 @@ pub enum Unit { Years, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct RelativeDate { /// The time unit to use for the offset (days, months, or years) pub unit: Unit, /// The quantity of time units to offset (must be a positive integer) - #[oai(validator(minimum(value = "1")))] + //#[oai(validator(minimum(value = "1")))] pub value: u32, } @@ -255,7 +257,7 @@ impl DateSince { #[cfg(test)] mod test { - use crate::modules::account::since::{DateSince, RelativeDate, Unit}; + use crate::account::since::{DateSince, RelativeDate, Unit}; #[test] fn test1() { diff --git a/src/modules/account/state.rs b/crates/core/src/account/state.rs similarity index 95% rename from src/modules/account/state.rs rename to crates/core/src/account/state.rs index 0ff079d..20500ab 100644 --- a/src/modules/account/state.rs +++ b/crates/core/src/account/state.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - modules::{ + { database::{async_find_impl, delete_impl, manager::DB_MANAGER, update_impl, upsert_impl}, error::{code::ErrorCode, BichonResult}, }, @@ -25,11 +25,11 @@ use crate::{ }; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum DownloadStatus { Running, Success, @@ -38,14 +38,16 @@ pub enum DownloadStatus { Cancelled, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum TriggerType { Manual, #[default] Scheduled, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum FolderStatus { #[default] Pending, @@ -55,7 +57,8 @@ pub enum FolderStatus { Cancelled, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct FolderProgress { pub folder_name: String, pub planned: u64, @@ -64,7 +67,8 @@ pub struct FolderProgress { pub message: Option, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct DownloadSession { pub start_time: i64, pub end_time: Option, @@ -76,7 +80,8 @@ pub struct DownloadSession { pub errors: Vec, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 3, version = 1)] #[native_db] pub struct DownloadState { @@ -89,7 +94,8 @@ pub struct DownloadState { pub global_errors: Vec, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccountError { pub error: String, pub at: i64, diff --git a/src/modules/account/view.rs b/crates/core/src/account/view.rs similarity index 97% rename from src/modules/account/view.rs rename to crates/core/src/account/view.rs index 6cbae1c..b594766 100644 --- a/src/modules/account/view.rs +++ b/crates/core/src/account/view.rs @@ -17,11 +17,9 @@ // along with this program. If not, see . use std::collections::{BTreeSet, HashMap}; - -use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use crate::modules::{ +use crate::{ account::{ entity::ImapConfig, migration::{AccountModel, AccountType, QuotaWindow}, @@ -30,7 +28,8 @@ use crate::modules::{ users::UserModel, }; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccountResp { pub id: u64, pub imap: Option, diff --git a/src/modules/cli/admin/meta.rs b/crates/core/src/admin/meta.rs similarity index 99% rename from src/modules/cli/admin/meta.rs rename to crates/core/src/admin/meta.rs index beea4a5..7ec75e8 100644 --- a/src/modules/cli/admin/meta.rs +++ b/crates/core/src/admin/meta.rs @@ -23,7 +23,7 @@ use std::{path::Path, rc::Rc}; use native_db::{Builder, Database}; use crate::{ - modules::{ + { database::META_MODELS, error::{code::ErrorCode, BichonResult}, token::{AccessTokenModel, AccessTokenModelKey, TokenType}, diff --git a/src/modules/cli/admin/mod.rs b/crates/core/src/admin/mod.rs similarity index 100% rename from src/modules/cli/admin/mod.rs rename to crates/core/src/admin/mod.rs diff --git a/src/modules/autoconfig/entity.rs b/crates/core/src/autoconfig/entity.rs similarity index 90% rename from src/modules/autoconfig/entity.rs rename to crates/core/src/autoconfig/entity.rs index d4d28d4..af053e2 100644 --- a/src/modules/autoconfig/entity.rs +++ b/crates/core/src/autoconfig/entity.rs @@ -18,12 +18,12 @@ use autoconfig::config::OAuth2Config as XOAuth2Config; -use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use crate::modules::account::entity::Encryption; +use crate::account::entity::Encryption; -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct ServerConfig { /// server hostname or IP address pub host: String, @@ -43,7 +43,8 @@ impl ServerConfig { } } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct OAuth2Config { /// The authorization server's issuer identifier URL pub issuer: String, @@ -66,7 +67,8 @@ impl From<&XOAuth2Config> for OAuth2Config { } } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct MailServerConfig { /// IMAP server configuration pub imap: ServerConfig, diff --git a/src/modules/autoconfig/load.rs b/crates/core/src/autoconfig/load.rs similarity index 96% rename from src/modules/autoconfig/load.rs rename to crates/core/src/autoconfig/load.rs index 24c063b..56cba84 100644 --- a/src/modules/autoconfig/load.rs +++ b/crates/core/src/autoconfig/load.rs @@ -17,10 +17,10 @@ // along with this program. If not, see . -use crate::modules::autoconfig::entity::{MailServerConfig, ServerConfig}; -use crate::modules::error::code::ErrorCode; +use crate::autoconfig::entity::{MailServerConfig, ServerConfig}; +use crate::error::code::ErrorCode; use crate::{ - modules::{ + { account::entity::Encryption, autoconfig::CachedMailSettings, error::BichonResult, }, raise_error, diff --git a/src/modules/autoconfig/mod.rs b/crates/core/src/autoconfig/mod.rs similarity index 90% rename from src/modules/autoconfig/mod.rs rename to crates/core/src/autoconfig/mod.rs index 4fd7b13..8473f21 100644 --- a/src/modules/autoconfig/mod.rs +++ b/crates/core/src/autoconfig/mod.rs @@ -17,12 +17,12 @@ // along with this program. If not, see . -use crate::modules::database::manager::DB_MANAGER; -use crate::modules::database::{delete_impl, async_find_impl, upsert_impl}; -use crate::modules::error::code::ErrorCode; +use crate::database::manager::DB_MANAGER; +use crate::database::{delete_impl, async_find_impl, upsert_impl}; +use crate::error::code::ErrorCode; use crate::raise_error; use crate::{ - modules::autoconfig::entity::MailServerConfig, modules::error::BichonResult, utc_now, + autoconfig::entity::MailServerConfig, error::BichonResult, utc_now, }; use native_db::*; use native_model::{native_model, Model}; diff --git a/src/modules/autoconfig/tests.rs b/crates/core/src/autoconfig/tests.rs similarity index 100% rename from src/modules/autoconfig/tests.rs rename to crates/core/src/autoconfig/tests.rs diff --git a/src/modules/cache/imap/download/download_folders.rs b/crates/core/src/cache/imap/download/download_folders.rs similarity index 99% rename from src/modules/cache/imap/download/download_folders.rs rename to crates/core/src/cache/imap/download/download_folders.rs index b41f7f4..c6c1eaf 100644 --- a/src/modules/cache/imap/download/download_folders.rs +++ b/crates/core/src/cache/imap/download/download_folders.rs @@ -20,7 +20,7 @@ use std::collections::BTreeSet; use crate::{ decode_mailbox_name, - modules::{ + { account::migration::{AccountModel, AccountType}, cache::imap::mailbox::{AttributeEnum, MailBox}, error::{code::ErrorCode, BichonResult}, diff --git a/src/modules/cache/imap/download/download_type.rs b/crates/core/src/cache/imap/download/download_type.rs similarity index 99% rename from src/modules/cache/imap/download/download_type.rs rename to crates/core/src/cache/imap/download/download_type.rs index 83200c2..346413e 100644 --- a/src/modules/cache/imap/download/download_type.rs +++ b/crates/core/src/cache/imap/download/download_type.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - modules::{ + { account::{ migration::AccountModel, state::{DownloadState, TriggerType}, diff --git a/src/modules/cache/imap/download/flow.rs b/crates/core/src/cache/imap/download/flow.rs similarity index 99% rename from src/modules/cache/imap/download/flow.rs rename to crates/core/src/cache/imap/download/flow.rs index 8a2500f..b0f2a7d 100644 --- a/src/modules/cache/imap/download/flow.rs +++ b/crates/core/src/cache/imap/download/flow.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - modules::{ + { account::{ migration::AccountModel, state::{DownloadState, DownloadStatus, FolderStatus}, diff --git a/src/modules/cache/imap/download/mod.rs b/crates/core/src/cache/imap/download/mod.rs similarity index 99% rename from src/modules/cache/imap/download/mod.rs rename to crates/core/src/cache/imap/download/mod.rs index 5482336..0cfe354 100644 --- a/src/modules/cache/imap/download/mod.rs +++ b/crates/core/src/cache/imap/download/mod.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ +use crate::{ account::{ migration::{AccountModel, AccountType}, state::{DownloadState, DownloadStatus}, diff --git a/src/modules/cache/imap/download/rebuild.rs b/crates/core/src/cache/imap/download/rebuild.rs similarity index 99% rename from src/modules/cache/imap/download/rebuild.rs rename to crates/core/src/cache/imap/download/rebuild.rs index 95f39e4..c4042ae 100644 --- a/src/modules/cache/imap/download/rebuild.rs +++ b/crates/core/src/cache/imap/download/rebuild.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - modules::{ + { account::{ migration::AccountModel, state::{DownloadState, DownloadStatus, FolderStatus}, diff --git a/src/modules/cache/imap/mailbox.rs b/crates/core/src/cache/imap/mailbox.rs similarity index 96% rename from src/modules/cache/imap/mailbox.rs rename to crates/core/src/cache/imap/mailbox.rs index 4ff48d7..d8f320f 100644 --- a/src/modules/cache/imap/mailbox.rs +++ b/crates/core/src/cache/imap/mailbox.rs @@ -18,7 +18,7 @@ use crate::{ decode_mailbox_name, encode_mailbox_name, - modules::{ + { database::{ async_filter_by_secondary_key_impl, async_find_impl, batch_delete_impl, batch_insert_impl, batch_upsert_impl, delete_impl, filter_by_secondary_key_impl, @@ -32,10 +32,10 @@ use async_imap::types::{Name, NameAttribute}; use itertools::Itertools; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 1, version = 1)] #[native_db] pub struct MailBox { @@ -145,7 +145,8 @@ impl MailBox { } } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct Attribute { pub attr: AttributeEnum, pub extension: Option, @@ -157,7 +158,8 @@ impl Attribute { } } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum AttributeEnum { NoInferiors, NoSelect, diff --git a/src/modules/cache/imap/mod.rs b/crates/core/src/cache/imap/mod.rs similarity index 86% rename from src/modules/cache/imap/mod.rs rename to crates/core/src/cache/imap/mod.rs index 41624bc..8ff116c 100644 --- a/src/modules/cache/imap/mod.rs +++ b/crates/core/src/cache/imap/mod.rs @@ -16,18 +16,20 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::sync::LazyLock; +use std::{ + collections::{HashMap, HashSet}, + sync::LazyLock, +}; -use crate::modules::{ - account::{state::DownloadState, old_state::AccountRunningState}, +use crate::{ + account::{old_state::AccountRunningState, state::DownloadState}, database::ModelsAdapter, }; -use ahash::{AHashMap, AHashSet}; use mailbox::MailBox; use native_db::Models; -pub mod mailbox; pub mod download; +pub mod mailbox; pub mod task; pub static MAILBOX_MODELS: LazyLock = LazyLock::new(|| { @@ -42,7 +44,7 @@ pub fn find_missing_mailboxes( local_mailboxes: &[MailBox], server_mailboxes: &[MailBox], ) -> Vec { - let local_names: AHashSet<_> = local_mailboxes.iter().map(|m| &m.name).collect(); + let local_names: HashSet<_> = local_mailboxes.iter().map(|m| &m.name).collect(); server_mailboxes .iter() .filter(|m| !local_names.contains(&m.name)) @@ -54,7 +56,7 @@ pub fn find_intersecting_mailboxes( local_mailboxes: &[MailBox], remote_mailboxes: &[MailBox], ) -> Vec<(MailBox, MailBox)> { - let local_map: AHashMap<_, _> = local_mailboxes + let local_map: HashMap<_, _> = local_mailboxes .iter() .map(|m| (m.name.clone(), m.clone())) .collect(); diff --git a/src/modules/cache/imap/task.rs b/crates/core/src/cache/imap/task.rs similarity index 94% rename from src/modules/cache/imap/task.rs rename to crates/core/src/cache/imap/task.rs index c9f450d..0c331ed 100644 --- a/src/modules/cache/imap/task.rs +++ b/crates/core/src/cache/imap/task.rs @@ -16,12 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::entity::AuthType; -use crate::modules::account::state::DownloadState; -use crate::modules::cache::imap::download::process_imap_download; -use crate::modules::common::periodic::{PeriodicTask, TaskHandle}; -use crate::modules::oauth2::token::OAuth2AccessToken; -use crate::modules::{account::migration::AccountModel, error::BichonResult}; +use crate::account::entity::AuthType; +use crate::account::state::DownloadState; +use crate::cache::imap::download::process_imap_download; +use crate::common::periodic::{PeriodicTask, TaskHandle}; +use crate::oauth2::token::OAuth2AccessToken; +use crate::{account::migration::AccountModel, error::BichonResult}; use crate::utc_now; use std::collections::HashMap; use std::sync::atomic::{AtomicI64, Ordering}; diff --git a/src/modules/cache/mod.rs b/crates/core/src/cache/mod.rs similarity index 96% rename from src/modules/cache/mod.rs rename to crates/core/src/cache/mod.rs index c151ff1..599ec46 100644 --- a/src/modules/cache/mod.rs +++ b/crates/core/src/cache/mod.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . -use crate::modules::settings::cli::SETTINGS; +use crate::settings::cli::SETTINGS; use std::sync::{Arc, LazyLock}; use tokio::sync::Semaphore; diff --git a/crates/core/src/common/auth.rs b/crates/core/src/common/auth.rs new file mode 100644 index 0000000..61b631f --- /dev/null +++ b/crates/core/src/common/auth.rs @@ -0,0 +1,143 @@ +use std::{ + collections::{BTreeSet, HashSet}, + net::IpAddr, +}; + +use crate::{ + error::{code::ErrorCode, BichonResult}, + raise_error, + users::{permissions::Permission, role::UserRole, UserModel}, +}; + +#[derive(Clone, Debug)] +pub struct ClientContext { + pub ip_addr: Option, + pub user: UserModel, +} + +impl ClientContext { + pub async fn require_any_permission( + &self, + requirements: Vec<(Option, &str)>, + ) -> BichonResult<()> { + for (account_id, permission) in requirements { + if self.has_permission(account_id, permission).await { + return Ok(()); + } + } + Err(raise_error!( + "Access denied: Insufficient permissions to perform this action.".into(), + ErrorCode::Forbidden + )) + } + + pub async fn check_has_permission( + user: &UserModel, + account_id: Option, + permission: &str, + ) -> bool { + if user.is_admin().await { + return true; + } + + let mut global_perms = HashSet::new(); + for rid in &user.global_roles { + if let Some(role) = UserRole::find(*rid).await.ok().flatten() { + global_perms.extend(role.permissions); + } + } + + if Self::check_global_logic(&global_perms, permission) { + return true; + } + + if let Some(aid) = account_id { + if let Some(role_id) = user.account_access_map.get(&aid) { + if let Some(role) = UserRole::find(*role_id).await.ok().flatten() { + if role.permissions.contains(&permission.to_string()) + || Self::check_account_logic(&role.permissions, permission) + { + return true; + } + } + } + } + + false + } + + pub async fn has_permission(&self, account_id: Option, permission: &str) -> bool { + if self.user.is_admin().await { + return true; + } + + let mut global_perms = HashSet::new(); + for rid in &self.user.global_roles { + if let Some(role) = UserRole::find(*rid).await.ok().flatten() { + global_perms.extend(role.permissions); + } + } + + if Self::check_global_logic(&global_perms, permission) { + return true; + } + + if let Some(aid) = account_id { + if let Some(role_id) = self.user.account_access_map.get(&aid) { + if let Some(role) = UserRole::find(*role_id).await.ok().flatten() { + if role.permissions.contains(&permission.to_string()) + || Self::check_account_logic(&role.permissions, permission) + { + return true; + } + } + } + } + + false + } + + fn check_global_logic(global: &HashSet, perm: &str) -> bool { + if global.contains(perm) { + return true; + } + + match perm { + Permission::DATA_READ => global.contains(Permission::DATA_READ_ALL), + Permission::DATA_DELETE => global.contains(Permission::DATA_DELETE_ALL), + Permission::DATA_RAW_DOWNLOAD => global.contains(Permission::DATA_RAW_DOWNLOAD_ALL), + Permission::DATA_EXPORT_BATCH => global.contains(Permission::DATA_EXPORT_BATCH_ALL), + Permission::ACCOUNT_MANAGE | Permission::ACCOUNT_READ_DETAILS => { + global.contains(Permission::ACCOUNT_MANAGE_ALL) + } + _ => false, + } + } + + fn check_account_logic(scoped_perms: &BTreeSet, perm: &str) -> bool { + if scoped_perms.contains(perm) { + return true; + } + match perm { + Permission::DATA_READ | Permission::ACCOUNT_READ_DETAILS => { + scoped_perms.contains(Permission::ACCOUNT_MANAGE) + } + _ => false, + } + } + + pub async fn require_permission( + &self, + account_id: Option, + permission: &str, + ) -> BichonResult<()> { + if self.has_permission(account_id, permission).await { + Ok(()) + } else { + Err(raise_error!( + format!("Access Denied: Missing permission '{}'", permission), + ErrorCode::Forbidden + )) + } + } +} diff --git a/src/modules/common/mod.rs b/crates/core/src/common/mod.rs similarity index 60% rename from src/modules/common/mod.rs rename to crates/core/src/common/mod.rs index 8b8e8f8..7545de7 100644 --- a/src/modules/common/mod.rs +++ b/crates/core/src/common/mod.rs @@ -16,29 +16,17 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use super::error::code::ErrorCode; -use super::error::BichonError; -use mail_parser::{Addr as ImapAddr, Address as ImapAddress}; -use poem::error::ResponseError; -use poem::Body; -use poem::{http::StatusCode, Error, Response}; -use poem_openapi::Object; -use serde::{Deserialize, Serialize}; use std::ops::Deref; -use tracing::error; +use mail_parser::{Addr as ImapAddr, Address as ImapAddress}; +use serde::{Deserialize, Serialize}; pub mod auth; -pub mod error; -pub mod log; pub mod paginated; pub mod periodic; pub mod rustls; pub mod signal; -pub mod timeout; -pub mod tls; -pub mod validator; -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Object)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Addr { /// The optional display name associated with the email address (e.g., "John Doe"). /// If `None`, no display name is specified. @@ -91,57 +79,3 @@ impl<'x> From<&ImapAddress<'x>> for AddrVec { AddrVec(vec) } } - -#[inline] -fn create_rust_mailer_error(message: &str, code: ErrorCode) -> BichonError { - BichonError::Generic { - message: message.into(), - location: snafu::location!(), - code, - } -} - -#[inline] -pub fn create_api_error_response(message: &str, code: ErrorCode) -> Error { - let rust_mailer_error = create_rust_mailer_error(message, code); - rust_mailer_error.into() -} - -impl ResponseError for BichonError { - fn status(&self) -> StatusCode { - match self { - BichonError::Generic { - message: _, - location: _, - code, - } => code.status(), - } - } - - fn as_response(&self) -> Response - where - Self: std::error::Error + Send + Sync + 'static, - { - match self { - BichonError::Generic { - message, - location, - code, - } => { - error!( - error_code = *code as u32, - error_message = %message, - error_location = ?location - ); - - let body = Body::from_json(serde_json::json!({ - "code": *code as u32, - "message": message.to_string(), - })) - .unwrap(); - - Response::builder().status(self.status()).body(body) - } - } - } -} diff --git a/crates/core/src/common/paginated.rs b/crates/core/src/common/paginated.rs new file mode 100644 index 0000000..2f67e7f --- /dev/null +++ b/crates/core/src/common/paginated.rs @@ -0,0 +1,180 @@ +// +// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) +// +// This file is part of the Bichon Email Archiving Project +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::{ + error::{code::ErrorCode, BichonResult}, + raise_error, +}; +use serde::{Deserialize, Serialize}; +use std::cmp::min; + +pub fn paginate_vec( + items: &Vec, + page: Option, + page_size: Option, +) -> BichonResult> { + let total_items = items.len() as u64; + + let (offset, total_pages) = match (page, page_size) { + (Some(p), Some(s)) if p > 0 && s > 0 => { + let offset = (p - 1) * s; + let total_pages = if total_items > 0 { + (total_items + s - 1) / s + } else { + 0 + }; + (Some(offset), Some(total_pages)) + } + (Some(0), _) | (_, Some(0)) => { + return Err(raise_error!( + "'page' and 'page_size' must be greater than 0.".into(), + ErrorCode::InvalidParameter + )); + } + _ => (None, None), + }; + + let data = match offset { + Some(offset) if offset >= total_items => vec![], + Some(offset) => { + let end = min(offset + page_size.unwrap_or(total_items), total_items) as usize; + items[offset as usize..end].to_vec() + } + None => items.clone(), + }; + + Ok(Paginated::new( + page, + page_size, + total_items, + total_pages, + data, + )) +} + + +#[cfg(not(feature = "web-api"))] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct DataPage +where + S: Serialize + std::fmt::Debug + std::marker::Unpin + Send + Sync, +{ + /// The current page number (starting from 1). + pub current_page: Option, + /// The number of items per page. + pub page_size: Option, + /// The total number of items across all pages. + pub total_items: u64, + /// The list of items returned on the current page. + pub items: Vec, + /// The total number of pages. This is optional and may not be set if not calculated. + pub total_pages: Option, +} +#[cfg(not(feature = "web-api"))] +impl From> + for DataPage +{ + fn from(paginated: Paginated) -> Self { + DataPage { + current_page: paginated.page, + page_size: paginated.page_size, + total_items: paginated.total_items, + total_pages: paginated.total_pages, + items: paginated.items, + } + } +} + + + + +#[cfg(feature = "web-api")] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, poem_openapi::Object)] +pub struct DataPage +where + S: Serialize + + std::fmt::Debug + + std::marker::Unpin + + Send + + Sync + + poem_openapi::types::Type + + poem_openapi::types::ParseFromJSON + + poem_openapi::types::ToJSON, +{ + /// The current page number (starting from 1). + pub current_page: Option, + /// The number of items per page. + pub page_size: Option, + /// The total number of items across all pages. + pub total_items: u64, + /// The list of items returned on the current page. + pub items: Vec, + /// The total number of pages. This is optional and may not be set if not calculated. + pub total_pages: Option, +} + +#[cfg(feature = "web-api")] +impl< + S: Serialize + + std::fmt::Debug + + std::marker::Unpin + + Send + + Sync + + poem_openapi::types::Type + + poem_openapi::types::ParseFromJSON + + poem_openapi::types::ToJSON, + > From> for DataPage +{ + fn from(paginated: Paginated) -> Self { + DataPage { + current_page: paginated.page, + page_size: paginated.page_size, + total_items: paginated.total_items, + total_pages: paginated.total_pages, + items: paginated.items, + } + } +} + + +#[derive(Debug)] +pub struct Paginated { + pub page: Option, + pub page_size: Option, + pub total_items: u64, + pub total_pages: Option, + pub items: Vec, +} + +impl Paginated { + pub fn new( + page: Option, + page_size: Option, + total_items: u64, + total_pages: Option, + items: Vec, + ) -> Self { + Paginated { + page, + page_size, + total_items, + total_pages, + items, + } + } +} diff --git a/src/modules/common/periodic.rs b/crates/core/src/common/periodic.rs similarity index 98% rename from src/modules/common/periodic.rs rename to crates/core/src/common/periodic.rs index 0009922..b36c2ab 100644 --- a/src/modules/common/periodic.rs +++ b/crates/core/src/common/periodic.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{common::signal::SIGNAL_MANAGER, error::BichonResult}; +use crate::{common::signal::SIGNAL_MANAGER, error::BichonResult}; use std::{future::Future, time::Duration}; use tokio::{sync::oneshot, task::JoinHandle, time::MissedTickBehavior}; use tracing::{info, warn}; diff --git a/src/modules/common/rustls.rs b/crates/core/src/common/rustls.rs similarity index 98% rename from src/modules/common/rustls.rs rename to crates/core/src/common/rustls.rs index 7fd75a2..9ad860e 100644 --- a/src/modules/common/rustls.rs +++ b/crates/core/src/common/rustls.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - modules::{ + { context::Initialize, error::{code::ErrorCode, BichonResult}, }, diff --git a/src/modules/common/signal.rs b/crates/core/src/common/signal.rs similarity index 94% rename from src/modules/common/signal.rs rename to crates/core/src/common/signal.rs index 25abe57..2deb0ce 100644 --- a/src/modules/common/signal.rs +++ b/crates/core/src/common/signal.rs @@ -18,7 +18,7 @@ use std::sync::LazyLock; -use crate::modules::{context::Initialize, error::BichonResult, utils::shutdown::shutdown_signal}; +use crate::{context::Initialize, error::BichonResult, utils::shutdown::shutdown_signal}; use tokio::sync::broadcast; pub static SIGNAL_MANAGER: LazyLock = LazyLock::new(SignalManager::new); diff --git a/src/modules/context/controller.rs b/crates/core/src/context/controller.rs similarity index 97% rename from src/modules/context/controller.rs rename to crates/core/src/context/controller.rs index eacd501..f63e531 100644 --- a/src/modules/context/controller.rs +++ b/crates/core/src/context/controller.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{cache::imap::task::SYNC_TASKS, error::BichonResult}; +use crate::{cache::imap::task::SYNC_TASKS, error::BichonResult}; use std::{sync::LazyLock, time::Duration}; use tokio::sync::mpsc; use tracing::{error, info}; diff --git a/src/modules/context/executors.rs b/crates/core/src/context/executors.rs similarity index 95% rename from src/modules/context/executors.rs rename to crates/core/src/context/executors.rs index 23624f0..aad48e2 100644 --- a/src/modules/context/executors.rs +++ b/crates/core/src/context/executors.rs @@ -16,10 +16,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::AccountType; -use crate::modules::context::Initialize; +use crate::account::migration::AccountType; +use crate::context::Initialize; use crate::{ - modules::{ + { account::migration::AccountModel, context::controller::DOWNLOAD_CONTROLLER, error::BichonResult, }, utc_now, diff --git a/src/modules/context/mod.rs b/crates/core/src/context/mod.rs similarity index 91% rename from src/modules/context/mod.rs rename to crates/core/src/context/mod.rs index 7a81019..b053525 100644 --- a/src/modules/context/mod.rs +++ b/crates/core/src/context/mod.rs @@ -16,11 +16,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{common::periodic::TaskHandle, error::BichonResult}; +use crate::{common::periodic::TaskHandle, error::BichonResult}; pub mod controller; pub mod executors; -pub mod status; #[allow(async_fn_in_trait)] pub trait Initialize { diff --git a/src/modules/dashboard/mod.rs b/crates/core/src/dashboard/mod.rs similarity index 95% rename from src/modules/dashboard/mod.rs rename to crates/core/src/dashboard/mod.rs index 4903e0b..bf92970 100644 --- a/src/modules/dashboard/mod.rs +++ b/crates/core/src/dashboard/mod.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ +use crate::{ store::tantivy::{ attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER, @@ -25,24 +25,24 @@ use crate::modules::{ }, users::permissions::Permission, }; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use tantivy::{schema::Value, TantivyDocument}; use crate::{ - bichon_version, - modules::{ + bichon_version, raise_error, + { account::migration::AccountModel, common::auth::ClientContext, error::{code::ErrorCode, BichonResult}, settings::dir::DATA_DIR_MANAGER, utils::get_total_size, }, - raise_error, }; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct DashboardStats { pub account_count: usize, // Number of accounts pub email_count: u64, // Total number of emails @@ -57,7 +57,6 @@ pub struct DashboardStats { pub top_largest_emails: Vec, // Top 10 largest emails pub top_largest_attachments: Vec, // Top 10 largest attachments pub system_version: String, // The semantic version string of the currently running backend service - pub commit_hash: String, // Git commit hash used to build this system version } impl DashboardStats { @@ -103,25 +102,27 @@ impl DashboardStats { } stat.system_version = bichon_version!().to_string(); - stat.commit_hash = env!("GIT_HASH").to_string(); Ok(stat) } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct TimeBucket { pub timestamp_ms: i64, // Timestamp in milliseconds pub count: u64, // Number of emails in this time bucket } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct Group { pub key: String, pub count: u64, // Number of emails from this sender } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct LargestEmail { pub subject: String, // Email subject pub size_bytes: u64, // Email size in bytes @@ -173,7 +174,8 @@ impl LargestEmail { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct LargestAttachment { pub name: String, // Attachment name pub size_bytes: u64, // Attachment size in bytes diff --git a/src/modules/database/manager.rs b/crates/core/src/database/manager.rs similarity index 91% rename from src/modules/database/manager.rs rename to crates/core/src/database/manager.rs index f4b7fc1..1925ae9 100644 --- a/src/modules/database/manager.rs +++ b/crates/core/src/database/manager.rs @@ -16,13 +16,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::AccountModel; -use crate::modules::cache::imap::MAILBOX_MODELS; -use crate::modules::error::{code::ErrorCode, BichonError}; -use crate::modules::settings::cli::SETTINGS; -use crate::modules::settings::dir::DATA_DIR_MANAGER; -use crate::modules::users::UserModel; -use crate::modules::{database::META_MODELS, error::BichonResult}; +use crate::account::migration::AccountModel; +use crate::cache::imap::MAILBOX_MODELS; +use crate::error::{code::ErrorCode, BichonError}; +use crate::settings::cli::SETTINGS; +use crate::settings::dir::DATA_DIR_MANAGER; +use crate::users::UserModel; +use crate::{database::META_MODELS, error::BichonResult}; use crate::raise_error; use native_db::{Builder, Database}; use std::sync::{Arc, LazyLock}; diff --git a/src/modules/database/mod.rs b/crates/core/src/database/mod.rs similarity index 94% rename from src/modules/database/mod.rs rename to crates/core/src/database/mod.rs index 12b3b10..12fb264 100644 --- a/src/modules/database/mod.rs +++ b/crates/core/src/database/mod.rs @@ -16,18 +16,19 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::{AccountV1, AccountV2, AccountV3, AccountV4}; -use crate::modules::autoconfig::CachedMailSettings; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::oauth2::entity::OAuth2; -use crate::modules::oauth2::pending::OAuth2PendingEntity; -use crate::modules::oauth2::token::OAuth2AccessToken; -use crate::modules::settings::proxy::Proxy; -use crate::modules::settings::system::SystemSetting; -use crate::modules::token::AccessTokenModel; -use crate::modules::users::role::UserRole; -use crate::modules::users::{BichonUser, BichonUserV2}; +use crate::account::migration::{AccountV1, AccountV2, AccountV3, AccountV4}; +use crate::autoconfig::CachedMailSettings; +use crate::common::paginated::Paginated; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::oauth2::entity::OAuth2; +use crate::oauth2::pending::OAuth2PendingEntity; +use crate::oauth2::token::OAuth2AccessToken; +use crate::settings::proxy::Proxy; +use crate::settings::system::SystemSetting; +use crate::token::AccessTokenModel; +use crate::users::role::UserRole; +use crate::users::{BichonUser, BichonUserV2}; use crate::raise_error; use db_type::{KeyOptions, ToKeyDefinition}; use itertools::Itertools; @@ -503,30 +504,3 @@ pub fn secondary_find_impl( .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(entities) } - -#[derive(Debug)] -pub struct Paginated { - pub page: Option, - pub page_size: Option, - pub total_items: u64, - pub total_pages: Option, - pub items: Vec, -} - -impl Paginated { - pub fn new( - page: Option, - page_size: Option, - total_items: u64, - total_pages: Option, - items: Vec, - ) -> Self { - Paginated { - page, - page_size, - total_items, - total_pages, - items, - } - } -} diff --git a/src/modules/envelope/extractor.rs b/crates/core/src/envelope/extractor.rs similarity index 96% rename from src/modules/envelope/extractor.rs rename to crates/core/src/envelope/extractor.rs index 3ad42ca..b28ee4c 100644 --- a/src/modules/envelope/extractor.rs +++ b/crates/core/src/envelope/extractor.rs @@ -16,18 +16,18 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::AddrVec; -use crate::modules::envelope::utils::normalize_subject; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::message::content::AttachmentInfo; -use crate::modules::store::storage::{DetachedEmail, BLOB_MANAGER}; -use crate::modules::store::tantivy::attachment::ATTACHMENT_MANAGER; -use crate::modules::store::tantivy::envelope::ENVELOPE_MANAGER; -use crate::modules::store::tantivy::model::{AttachmentModel, EnvelopeWithAttachments}; -use crate::modules::utils::html::extract_text; -use crate::modules::utils::{compute_content_hash, hex_hash}; -use crate::{id, modules::store::envelope::Envelope}; +use crate::common::AddrVec; +use crate::envelope::utils::normalize_subject; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::message::content::AttachmentInfo; +use crate::store::storage::{DetachedEmail, BLOB_MANAGER}; +use crate::store::tantivy::attachment::ATTACHMENT_MANAGER; +use crate::store::tantivy::envelope::ENVELOPE_MANAGER; +use crate::store::tantivy::model::{AttachmentModel, EnvelopeWithAttachments}; +use crate::utils::html::extract_text; +use crate::utils::{compute_content_hash, hex_hash}; +use crate::{id, store::envelope::Envelope}; use crate::{raise_error, utc_now}; use async_imap::types::Fetch; use bytes::Bytes; diff --git a/src/modules/envelope/mod.rs b/crates/core/src/envelope/mod.rs similarity index 100% rename from src/modules/envelope/mod.rs rename to crates/core/src/envelope/mod.rs diff --git a/src/modules/envelope/utils.rs b/crates/core/src/envelope/utils.rs similarity index 98% rename from src/modules/envelope/utils.rs rename to crates/core/src/envelope/utils.rs index ffa0ff3..4623d14 100644 --- a/src/modules/envelope/utils.rs +++ b/crates/core/src/envelope/utils.rs @@ -90,7 +90,7 @@ pub fn normalize_subject(raw_subject: Option<&str>) -> String { #[cfg(test)] mod tests { - use crate::modules::envelope::utils::merge_contiguous_encoded_words; + use crate::envelope::utils::merge_contiguous_encoded_words; #[tokio::test] diff --git a/crates/core/src/error/code.rs b/crates/core/src/error/code.rs new file mode 100644 index 0000000..490c427 --- /dev/null +++ b/crates/core/src/error/code.rs @@ -0,0 +1,44 @@ +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum ErrorCode { + // Client-side errors (10000–10999) + InvalidParameter = 10000, + MissingConfiguration = 10020, + Incompatible = 10030, + PayloadTooLarge = 10070, + RequestTimeout = 10080, + MethodNotAllowed = 10090, + + // Authentication and authorization errors (20000–20999) + PermissionDenied = 20000, + AccountDisabled = 20010, + Forbidden = 20020, + OAuth2ItemDisabled = 20050, + MissingRefreshToken = 20060, + + // Resource errors (30000–30999) + ResourceNotFound = 30000, + TooManyRequest = 30020, + AlreadyExists = 30030, + + // Network connection errors (40000–40999) + NetworkError = 40000, + ConnectionTimeout = 40010, + ConnectionPoolTimeout = 40020, + HttpResponseError = 40030, + + // Mail service errors (50000–50999) + ImapCommandFailed = 50000, + ImapAuthenticationFailed = 50010, + ImapUnexpectedResult = 50020, + AutoconfigFetchFailed = 50060, + // Internal system errors (70000–70999) + InternalError = 70000, + UnhandledPoemError = 70010, +} + +impl ErrorCode { + pub fn to_u32(&self) -> u32 { + *self as u32 + } +} diff --git a/crates/core/src/error/mod.rs b/crates/core/src/error/mod.rs new file mode 100644 index 0000000..edf360c --- /dev/null +++ b/crates/core/src/error/mod.rs @@ -0,0 +1,19 @@ +use snafu::{Location, Snafu}; + +use crate::error::code::ErrorCode; + +pub mod code; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum BichonError { + #[snafu(display("{message}"))] + Generic { + message: String, + #[snafu(implicit)] + location: Location, + code: ErrorCode, + }, +} + +pub type BichonResult = std::result::Result; diff --git a/src/modules/imap/capabilities.rs b/crates/core/src/imap/capabilities.rs similarity index 91% rename from src/modules/imap/capabilities.rs rename to crates/core/src/imap/capabilities.rs index a2bbbf9..e7ffc53 100644 --- a/src/modules/imap/capabilities.rs +++ b/crates/core/src/imap/capabilities.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::error::code::ErrorCode; -use crate::modules::imap::session::SessionStream; -use crate::{modules::error::BichonResult, raise_error}; +use crate::error::code::ErrorCode; +use crate::imap::session::SessionStream; +use crate::{error::BichonResult, raise_error}; use async_imap::types::Capability; use async_imap::{types::Capabilities, Session}; diff --git a/src/modules/imap/client.rs b/crates/core/src/imap/client.rs similarity index 95% rename from src/modules/imap/client.rs rename to crates/core/src/imap/client.rs index 157ac42..1fd70b0 100644 --- a/src/modules/imap/client.rs +++ b/crates/core/src/imap/client.rs @@ -16,14 +16,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::entity::Encryption; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::imap::session::SessionStream; -use crate::modules::imap::stats::StatsWrapper; -use crate::modules::utils::net::establish_tcp_connection_with_timeout; -use crate::modules::utils::net::establish_tls_connection; -use crate::modules::utils::tls::establish_tls_stream; +use crate::account::entity::Encryption; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::imap::session::SessionStream; +use crate::imap::stats::StatsWrapper; +use crate::utils::net::establish_tcp_connection_with_timeout; +use crate::utils::net::establish_tls_connection; +use crate::utils::tls::establish_tls_stream; use crate::raise_error; use async_imap::Client as ImapClient; use async_imap::Session as ImapSession; diff --git a/src/modules/imap/executor.rs b/crates/core/src/imap/executor.rs similarity index 95% rename from src/modules/imap/executor.rs rename to crates/core/src/imap/executor.rs index c7dcf11..5ad2ef0 100644 --- a/src/modules/imap/executor.rs +++ b/crates/core/src/imap/executor.rs @@ -16,14 +16,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::AccountModel; -use crate::modules::account::state::{DownloadState, FolderStatus}; -use crate::modules::cache::imap::mailbox::MailBox; -use crate::modules::cache::imap::download::flow::{generate_uid_sequence_hashset, DEFAULT_BATCH_SIZE}; -use crate::modules::envelope::extractor::extract_envelope_and_store_it; -use crate::modules::error::code::ErrorCode; -use crate::modules::imap::session::SessionStream; -use crate::modules::{error::BichonResult, imap::manager::ImapConnectionManager}; +use crate::account::migration::AccountModel; +use crate::account::state::{DownloadState, FolderStatus}; +use crate::cache::imap::mailbox::MailBox; +use crate::cache::imap::download::flow::{generate_uid_sequence_hashset, DEFAULT_BATCH_SIZE}; +use crate::envelope::extractor::extract_envelope_and_store_it; +use crate::error::code::ErrorCode; +use crate::imap::session::SessionStream; +use crate::{error::BichonResult, imap::manager::ImapConnectionManager}; use crate::raise_error; use async_imap::types::Name; use async_imap::Session; diff --git a/src/modules/imap/manager.rs b/crates/core/src/imap/manager.rs similarity index 92% rename from src/modules/imap/manager.rs rename to crates/core/src/imap/manager.rs index d45b929..ac2f1cb 100644 --- a/src/modules/imap/manager.rs +++ b/crates/core/src/imap/manager.rs @@ -16,17 +16,17 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::entity::AuthType; -use crate::modules::account::migration::{AccountModel, AccountType}; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::imap::capabilities::{ +use crate::account::entity::AuthType; +use crate::account::migration::{AccountModel, AccountType}; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::imap::capabilities::{ capability_to_string, check_capabilities, fetch_capabilities, }; -use crate::modules::imap::client::Client; -use crate::modules::imap::oauth2::OAuth2; -use crate::modules::imap::session::SessionStream; -use crate::modules::oauth2::token::OAuth2AccessToken; +use crate::imap::client::Client; +use crate::imap::oauth2::OAuth2; +use crate::imap::session::SessionStream; +use crate::oauth2::token::OAuth2AccessToken; use crate::{bichon_version, decrypt, raise_error}; use async_imap::Session; use tracing::{error, warn}; diff --git a/src/modules/imap/mod.rs b/crates/core/src/imap/mod.rs similarity index 100% rename from src/modules/imap/mod.rs rename to crates/core/src/imap/mod.rs diff --git a/src/modules/imap/oauth2.rs b/crates/core/src/imap/oauth2.rs similarity index 100% rename from src/modules/imap/oauth2.rs rename to crates/core/src/imap/oauth2.rs diff --git a/src/modules/imap/session.rs b/crates/core/src/imap/session.rs similarity index 100% rename from src/modules/imap/session.rs rename to crates/core/src/imap/session.rs diff --git a/src/modules/imap/stats.rs b/crates/core/src/imap/stats.rs similarity index 98% rename from src/modules/imap/stats.rs rename to crates/core/src/imap/stats.rs index 63253f4..541852e 100644 --- a/src/modules/imap/stats.rs +++ b/crates/core/src/imap/stats.rs @@ -23,7 +23,7 @@ use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use crate::modules::imap::session::SessionStream; +use crate::imap::session::SessionStream; pub struct StatsWrapper { inner: T, diff --git a/src/modules/imap/tests.rs b/crates/core/src/imap/tests.rs similarity index 99% rename from src/modules/imap/tests.rs rename to crates/core/src/imap/tests.rs index f9fe96b..7b20d90 100644 --- a/src/modules/imap/tests.rs +++ b/crates/core/src/imap/tests.rs @@ -20,7 +20,7 @@ use mail_parser::{parsers::MessageStream, MessageParser, MimeHeaders}; use crate::{ base64_encode_url_safe, - modules::{account::entity::Encryption, imap::client::Client}, + {account::entity::Encryption, imap::client::Client}, }; #[tokio::test] diff --git a/src/modules/import/mod.rs b/crates/core/src/import/mod.rs similarity index 95% rename from src/modules/import/mod.rs rename to crates/core/src/import/mod.rs index 99b5ff9..2f74a9d 100644 --- a/src/modules/import/mod.rs +++ b/crates/core/src/import/mod.rs @@ -17,12 +17,12 @@ // along with this program. If not, see . -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use crate::{ base64_decode_url_safe, - modules::{ + { account::migration::{AccountModel, AccountType}, cache::imap::mailbox::{Attribute, AttributeEnum, MailBox}, envelope::extractor::extract_envelope_from_eml, @@ -32,7 +32,8 @@ use crate::{ raise_error, }; -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct BatchEmlRequest { pub account_id: u64, pub mail_folder: String, @@ -40,7 +41,8 @@ pub struct BatchEmlRequest { pub emls: Vec, } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct FailedEmlDetail { /// The 0-based index of the failed EML in the request list pub index: usize, @@ -48,7 +50,8 @@ pub struct FailedEmlDetail { pub error_message: String, } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct BatchEmlResult { /// Total number of emails processed pub total: usize, diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..b9b8033 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,23 @@ +pub mod account; +pub mod admin; +pub mod autoconfig; +pub mod cache; +pub mod common; +pub mod context; +pub mod dashboard; +pub mod database; +pub mod envelope; +pub mod error; +pub mod imap; +pub mod import; +pub mod logger; +pub mod mailbox; +pub mod message; +pub mod oauth2; +pub mod settings; +pub mod store; +pub mod tasks; +pub mod token; +pub mod users; +pub mod utils; +pub mod version; diff --git a/src/modules/logger/file.rs b/crates/core/src/logger/file.rs similarity index 94% rename from src/modules/logger/file.rs rename to crates/core/src/logger/file.rs index 001bc03..8aadaa6 100644 --- a/src/modules/logger/file.rs +++ b/crates/core/src/logger/file.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::logger::LocalTimer; -use crate::modules::settings::cli::SETTINGS; -use crate::modules::settings::dir::DATA_DIR_MANAGER; +use crate::logger::LocalTimer; +use crate::settings::cli::SETTINGS; +use crate::settings::dir::DATA_DIR_MANAGER; use std::sync::OnceLock; use tracing::level_filters::LevelFilter; use tracing::Level; diff --git a/src/modules/logger/mod.rs b/crates/core/src/logger/mod.rs similarity index 96% rename from src/modules/logger/mod.rs rename to crates/core/src/logger/mod.rs index e0851a3..ed3ec3e 100644 --- a/src/modules/logger/mod.rs +++ b/crates/core/src/logger/mod.rs @@ -16,8 +16,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::logger::file::setup_file_logger; -use crate::modules::settings::cli::SETTINGS; +use crate::logger::file::setup_file_logger; +use crate::settings::cli::SETTINGS; use chrono::Local; use std::process; use tracing::Level; diff --git a/src/modules/mailbox/delete.rs b/crates/core/src/mailbox/delete.rs similarity index 98% rename from src/modules/mailbox/delete.rs rename to crates/core/src/mailbox/delete.rs index ea19f38..3bf9160 100644 --- a/src/modules/mailbox/delete.rs +++ b/crates/core/src/mailbox/delete.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ +use crate::{ cache::imap::mailbox::MailBox, error::BichonResult, store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER}, diff --git a/src/modules/mailbox/list.rs b/crates/core/src/mailbox/list.rs similarity index 88% rename from src/modules/mailbox/list.rs rename to crates/core/src/mailbox/list.rs index 542a615..d3226c6 100644 --- a/src/modules/mailbox/list.rs +++ b/crates/core/src/mailbox/list.rs @@ -16,13 +16,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::{AccountModel, AccountType}; -use crate::modules::cache::imap::mailbox::{Attribute, AttributeEnum, MailBox}; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::imap::executor::ImapExecutor; -use crate::modules::imap::session::SessionStream; -use crate::modules::utils::create_hash; +use crate::account::migration::{AccountModel, AccountType}; +use crate::cache::imap::mailbox::{Attribute, AttributeEnum, MailBox}; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::imap::executor::ImapExecutor; +use crate::imap::session::SessionStream; +use crate::utils::create_hash; use crate::raise_error; use async_imap::types::Name; use async_imap::Session; diff --git a/src/modules/mailbox/mod.rs b/crates/core/src/mailbox/mod.rs similarity index 100% rename from src/modules/mailbox/mod.rs rename to crates/core/src/mailbox/mod.rs diff --git a/src/modules/message/append.rs b/crates/core/src/message/append.rs similarity index 95% rename from src/modules/message/append.rs rename to crates/core/src/message/append.rs index 1544a1f..cd59c81 100644 --- a/src/modules/message/append.rs +++ b/crates/core/src/message/append.rs @@ -1,6 +1,6 @@ use crate::{ encode_mailbox_name, - modules::{ + { account::migration::{AccountModel, AccountType}, envelope::extractor::reattach_eml_content, error::{code::ErrorCode, BichonResult}, @@ -8,12 +8,13 @@ use crate::{ }, raise_error, }; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; const MAX_RESTORE_COUNT: usize = 100; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct RestoreMessagesRequest { /// envelope IDs to restore (max 100) pub envelope_ids: Vec, diff --git a/src/modules/message/attachment.rs b/crates/core/src/message/attachment.rs similarity index 97% rename from src/modules/message/attachment.rs rename to crates/core/src/message/attachment.rs index 0ae718d..28dd206 100644 --- a/src/modules/message/attachment.rs +++ b/crates/core/src/message/attachment.rs @@ -1,7 +1,7 @@ use std::io::Cursor; use crate::{ - modules::{ + { dashboard::Group, envelope::extractor::reattach_eml_content, error::{code::ErrorCode, BichonResult}, @@ -11,10 +11,11 @@ use crate::{ }; use bytes::Bytes; use mail_parser::MessageParser; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AttachmentMetadata { /// Statistics of attachment file extensions (key + count). /// Each item represents a file extension and its occurrence count. diff --git a/src/modules/message/contacts.rs b/crates/core/src/message/contacts.rs similarity index 81% rename from src/modules/message/contacts.rs rename to crates/core/src/message/contacts.rs index ccfe081..7dace5b 100644 --- a/src/modules/message/contacts.rs +++ b/crates/core/src/message/contacts.rs @@ -1,7 +1,7 @@ -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct Contact { pub email: String, pub name: Option, diff --git a/src/modules/message/content.rs b/crates/core/src/message/content.rs similarity index 96% rename from src/modules/message/content.rs rename to crates/core/src/message/content.rs index 69a5b7f..2c65594 100644 --- a/src/modules/message/content.rs +++ b/crates/core/src/message/content.rs @@ -17,23 +17,24 @@ // along with this program. If not, see . use crate::base64_encode; -use crate::modules::account::migration::AccountModel; -use crate::modules::envelope::extractor::{ +use crate::account::migration::AccountModel; +use crate::envelope::extractor::{ extract_envelope_from_nested_message, reattach_eml_content, }; -use crate::modules::error::code::ErrorCode; -use crate::modules::store::envelope::Envelope; -use crate::modules::utils::compute_content_hash; -use crate::{modules::error::BichonResult, raise_error}; +use crate::error::code::ErrorCode; +use crate::store::envelope::Envelope; +use crate::utils::compute_content_hash; +use crate::{error::BichonResult, raise_error}; use mail_parser::{MessageParser, MimeHeaders}; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; /// Represents metadata of an attachment in a Gmail message. /// /// This struct stores information required to identify, download, /// and render an attachment, including inline images embedded /// in HTML emails. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AttachmentInfo { /// MIME content type of the attachment (e.g., `image/png`, `application/pdf`). pub file_type: String, @@ -134,7 +135,8 @@ impl AttachmentInfo { /// /// - `plain`: The plain text version of the message, if available. /// - `html`: The HTML version of the message, if available. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct FullMessageContent { /// Optional plain text version of the message. pub text: Option, @@ -144,7 +146,8 @@ pub struct FullMessageContent { pub attachments: Option>, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct FullNestedMessageContent { /// Optional plain text version of the message. pub text: Option, diff --git a/src/modules/message/delete.rs b/crates/core/src/message/delete.rs similarity index 86% rename from src/modules/message/delete.rs rename to crates/core/src/message/delete.rs index f998fdd..cc768f6 100644 --- a/src/modules/message/delete.rs +++ b/crates/core/src/message/delete.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::error::BichonResult; -use crate::modules::store::tantivy::attachment::ATTACHMENT_MANAGER; -use crate::modules::store::tantivy::envelope::ENVELOPE_MANAGER; +use crate::error::BichonResult; +use crate::store::tantivy::attachment::ATTACHMENT_MANAGER; +use crate::store::tantivy::envelope::ENVELOPE_MANAGER; use std::collections::HashMap; pub async fn delete_messages_impl(request: HashMap>) -> BichonResult<()> { diff --git a/src/modules/message/list.rs b/crates/core/src/message/list.rs similarity index 85% rename from src/modules/message/list.rs rename to crates/core/src/message/list.rs index 836c1d1..47c4f04 100644 --- a/src/modules/message/list.rs +++ b/crates/core/src/message/list.rs @@ -16,11 +16,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ - account::migration::AccountModel, - error::BichonResult, - rest::response::DataPage, - store::{envelope::Envelope, tantivy::envelope::ENVELOPE_MANAGER}, +use crate::{ + account::migration::AccountModel, common::paginated::DataPage, error::BichonResult, store::{envelope::Envelope, tantivy::envelope::ENVELOPE_MANAGER} }; pub async fn get_thread_messages( diff --git a/src/modules/message/mod.rs b/crates/core/src/message/mod.rs similarity index 100% rename from src/modules/message/mod.rs rename to crates/core/src/message/mod.rs diff --git a/src/modules/message/search.rs b/crates/core/src/message/search.rs similarity index 91% rename from src/modules/message/search.rs rename to crates/core/src/message/search.rs index 85c82a9..07eff21 100644 --- a/src/modules/message/search.rs +++ b/crates/core/src/message/search.rs @@ -16,25 +16,21 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use poem_openapi::{Enum, Object}; +//use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use crate::{ - modules::{ - error::{code::ErrorCode, BichonResult}, - rest::response::DataPage, - store::{ + common::paginated::DataPage, error::{BichonResult, code::ErrorCode}, raise_error, store::{ envelope::Envelope, tantivy::{ attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER, model::AttachmentModel, }, - }, - }, - raise_error, + } }; -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct EmailSearchFilter { pub text: Option, pub subject: Option, @@ -59,14 +55,16 @@ pub struct EmailSearchFilter { pub attachment_content_type: Option, } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Enum)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum SortBy { #[default] DATE, SIZE, } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct EmailSearchRequest { filter: EmailSearchFilter, page: u64, @@ -110,7 +108,8 @@ pub async fn search_messages_impl( .await } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AttachmentSearchFilter { pub id: Option, pub text: Option, @@ -139,7 +138,8 @@ pub struct AttachmentSearchFilter { pub max_page_count: Option, } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AttachmentSearchRequest { filter: AttachmentSearchFilter, page: u64, diff --git a/src/modules/message/tags.rs b/crates/core/src/message/tags.rs similarity index 80% rename from src/modules/message/tags.rs rename to crates/core/src/message/tags.rs index c048567..52864d9 100644 --- a/src/modules/message/tags.rs +++ b/crates/core/src/message/tags.rs @@ -18,23 +18,26 @@ use std::collections::HashMap; -use poem_openapi::{Enum, Object}; +//use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct TagsRequest { pub updates: HashMap>, pub tags: Vec, pub action: TagAction, } -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct TagCount { pub tag: String, pub count: u64, } -#[derive(Enum, Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum TagAction { Add, Remove, diff --git a/src/modules/oauth2/entity.rs b/crates/core/src/oauth2/entity.rs similarity index 95% rename from src/modules/oauth2/entity.rs rename to crates/core/src/oauth2/entity.rs index f3cb806..2b689d5 100644 --- a/src/modules/oauth2/entity.rs +++ b/crates/core/src/oauth2/entity.rs @@ -17,25 +17,24 @@ // along with this program. If not, see . use crate::{ - encrypt, id, - modules::{ - database::{ - async_secondary_find_impl, delete_impl, insert_impl, manager::DB_MANAGER, - paginate_query_primary_scan_all_impl, update_impl, - }, - error::{code::ErrorCode, BichonResult}, - rest::response::DataPage, + common::paginated::DataPage, + database::{ + async_secondary_find_impl, delete_impl, insert_impl, manager::DB_MANAGER, + paginate_query_primary_scan_all_impl, update_impl, }, - raise_error, utc_now, + encrypt, + error::{code::ErrorCode, BichonResult}, + id, raise_error, utc_now, }; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// Represents the OAuth2 configuration for a client, including initialization and runtime values. -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 5, version = 1)] #[native_db(primary_key(pk -> String))] pub struct OAuth2 { @@ -175,7 +174,8 @@ impl OAuth2 { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct OAuth2CreateRequest { /// A description of what this configuration is used for. pub description: Option, @@ -225,7 +225,8 @@ impl OAuth2CreateRequest { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct OAuth2UpdateRequest { /// A description of what this configuration is used for. pub description: Option, diff --git a/src/modules/oauth2/flow.rs b/crates/core/src/oauth2/flow.rs similarity index 97% rename from src/modules/oauth2/flow.rs rename to crates/core/src/oauth2/flow.rs index 4aa310c..9bc92c3 100644 --- a/src/modules/oauth2/flow.rs +++ b/crates/core/src/oauth2/flow.rs @@ -17,18 +17,18 @@ // along with this program. If not, see . -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::oauth2::{ +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::oauth2::{ entity::OAuth2, pending::OAuth2PendingEntity, token::OAuth2AccessToken, }; -use crate::modules::settings::proxy::Proxy; +use crate::settings::proxy::Proxy; use crate::{decrypt, encrypt, raise_error}; use oauth2::{ basic::BasicClient, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, TokenResponse, TokenUrl, }; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; pub type OAuth2Client = oauth2::Client< @@ -47,7 +47,8 @@ pub type OAuth2Client = oauth2::Client< oauth2::EndpointSet, >; -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AuthorizeUrlRequest { /// The ID of the account for which the authorization URL is generated. pub account_id: u64, diff --git a/src/modules/oauth2/mod.rs b/crates/core/src/oauth2/mod.rs similarity index 100% rename from src/modules/oauth2/mod.rs rename to crates/core/src/oauth2/mod.rs diff --git a/src/modules/oauth2/pending.rs b/crates/core/src/oauth2/pending.rs similarity index 99% rename from src/modules/oauth2/pending.rs rename to crates/core/src/oauth2/pending.rs index 5014cbb..2473d4b 100644 --- a/src/modules/oauth2/pending.rs +++ b/crates/core/src/oauth2/pending.rs @@ -18,7 +18,7 @@ use crate::{ - modules::{ + { database::{ batch_delete_impl, delete_impl, async_find_impl, insert_impl, manager::DB_MANAGER, }, diff --git a/src/modules/oauth2/refresh/mod.rs b/crates/core/src/oauth2/refresh/mod.rs similarity index 94% rename from src/modules/oauth2/refresh/mod.rs rename to crates/core/src/oauth2/refresh/mod.rs index 70fa260..8051281 100644 --- a/src/modules/oauth2/refresh/mod.rs +++ b/crates/core/src/oauth2/refresh/mod.rs @@ -16,10 +16,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::periodic::{PeriodicTask, TaskHandle}; -use crate::modules::context::BichonTask; -use crate::modules::oauth2::token::EXTERNAL_OAUTH_APP_ID; -use crate::modules::oauth2::{flow::OAuth2Flow, token::OAuth2AccessToken}; +use crate::common::periodic::{PeriodicTask, TaskHandle}; +use crate::context::BichonTask; +use crate::oauth2::token::EXTERNAL_OAUTH_APP_ID; +use crate::oauth2::{flow::OAuth2Flow, token::OAuth2AccessToken}; use crate::utc_now; use std::time::Duration; use tracing::{debug, error, info}; diff --git a/src/modules/oauth2/task.rs b/crates/core/src/oauth2/task.rs similarity index 98% rename from src/modules/oauth2/task.rs rename to crates/core/src/oauth2/task.rs index 6f81f3a..1142e90 100644 --- a/src/modules/oauth2/task.rs +++ b/crates/core/src/oauth2/task.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ +use crate::{ common::periodic::{PeriodicTask, TaskHandle}, context::BichonTask, oauth2::pending::OAuth2PendingEntity, diff --git a/src/modules/oauth2/token.rs b/crates/core/src/oauth2/token.rs similarity index 97% rename from src/modules/oauth2/token.rs rename to crates/core/src/oauth2/token.rs index 44b7b0f..237657e 100644 --- a/src/modules/oauth2/token.rs +++ b/crates/core/src/oauth2/token.rs @@ -19,7 +19,7 @@ use crate::{ decrypt, encrypt, - modules::{ + { database::{ async_find_impl, delete_impl, insert_impl, list_all_impl, manager::DB_MANAGER, update_impl, upsert_impl, @@ -31,12 +31,13 @@ use crate::{ }; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; pub const EXTERNAL_OAUTH_APP_ID: u64 = 0; -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 7, version = 1)] #[native_db] pub struct OAuth2AccessToken { @@ -195,7 +196,8 @@ impl OAuth2AccessToken { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct ExternalOAuth2Request { /// The id of the OAuth2 configuration associated with this access token. pub oauth2_id: Option, @@ -245,7 +247,7 @@ impl ExternalOAuth2Request { #[cfg(test)] mod tests { - use crate::modules::oauth2::token::OAuth2AccessToken; + use crate::oauth2::token::OAuth2AccessToken; #[tokio::test] async fn test1() { diff --git a/src/modules/settings/cli.rs b/crates/core/src/settings/cli.rs similarity index 99% rename from src/modules/settings/cli.rs rename to crates/core/src/settings/cli.rs index 2a7df79..e43c9f4 100644 --- a/src/modules/settings/cli.rs +++ b/crates/core/src/settings/cli.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::settings::io::check_dir_read_write; +use crate::settings::io::check_dir_read_write; use clap::{builder::ValueParser, Parser, ValueEnum}; use std::{collections::HashSet, env, fmt, path::PathBuf, sync::LazyLock}; diff --git a/src/modules/settings/dir.rs b/crates/core/src/settings/dir.rs similarity index 96% rename from src/modules/settings/dir.rs rename to crates/core/src/settings/dir.rs index eb926b0..f973154 100644 --- a/src/modules/settings/dir.rs +++ b/crates/core/src/settings/dir.rs @@ -16,10 +16,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::context::Initialize; -use crate::modules::settings::cli::SETTINGS; +use crate::context::Initialize; +use crate::settings::cli::SETTINGS; use crate::{ - modules::error::{code::ErrorCode, BichonResult}, + error::{code::ErrorCode, BichonResult}, raise_error, }; use std::path::PathBuf; diff --git a/src/modules/settings/io.rs b/crates/core/src/settings/io.rs similarity index 100% rename from src/modules/settings/io.rs rename to crates/core/src/settings/io.rs diff --git a/src/modules/settings/mod.rs b/crates/core/src/settings/mod.rs similarity index 96% rename from src/modules/settings/mod.rs rename to crates/core/src/settings/mod.rs index 151234e..be7d9bb 100644 --- a/src/modules/settings/mod.rs +++ b/crates/core/src/settings/mod.rs @@ -16,8 +16,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::settings::cli::Settings; -use poem_openapi::Object; +use crate::settings::cli::Settings; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; pub mod cli; @@ -25,7 +25,8 @@ pub mod dir; pub mod io; pub mod proxy; pub mod system; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct SystemConfigurations { pub bichon_log_level: String, pub bichon_http_port: i32, diff --git a/src/modules/settings/proxy.rs b/crates/core/src/settings/proxy.rs similarity index 97% rename from src/modules/settings/proxy.rs rename to crates/core/src/settings/proxy.rs index 9fbbdeb..49ee80d 100644 --- a/src/modules/settings/proxy.rs +++ b/crates/core/src/settings/proxy.rs @@ -18,12 +18,12 @@ use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use crate::{ id, - modules::{ + { database::{ async_find_impl, delete_impl, insert_impl, list_all_impl, manager::DB_MANAGER, update_impl, @@ -34,7 +34,8 @@ use crate::{ raise_error, utc_now, }; -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 8, version = 1)] #[native_db] pub struct Proxy { diff --git a/src/modules/settings/system.rs b/crates/core/src/settings/system.rs similarity index 92% rename from src/modules/settings/system.rs rename to crates/core/src/settings/system.rs index 025df65..115040f 100644 --- a/src/modules/settings/system.rs +++ b/crates/core/src/settings/system.rs @@ -16,11 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - -// use crate::modules::database::manager::DB_MANAGER; -// use crate::modules::database::{find_impl, upsert_impl}; -// use crate::modules::error::BichonResult; -// use crate::utc_now; use native_db::*; use native_model::{native_model, Model}; use serde::{Deserialize, Serialize}; diff --git a/src/modules/store/envelope.rs b/crates/core/src/store/envelope.rs similarity index 94% rename from src/modules/store/envelope.rs rename to crates/core/src/store/envelope.rs index ac02919..6767f29 100644 --- a/src/modules/store/envelope.rs +++ b/crates/core/src/store/envelope.rs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use tantivy::doc; -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct Envelope { pub id: String, pub message_id: String, diff --git a/src/modules/store/mod.rs b/crates/core/src/store/mod.rs similarity index 100% rename from src/modules/store/mod.rs rename to crates/core/src/store/mod.rs diff --git a/src/modules/store/storage.rs b/crates/core/src/store/storage.rs similarity index 99% rename from src/modules/store/storage.rs rename to crates/core/src/store/storage.rs index 0f0823b..e0f0a03 100644 --- a/src/modules/store/storage.rs +++ b/crates/core/src/store/storage.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ +use crate::{ common::signal::SIGNAL_MANAGER, envelope::extractor::reattach_eml_content, error::{code::ErrorCode, BichonResult}, diff --git a/src/modules/store/tantivy/attachment.rs b/crates/core/src/store/tantivy/attachment.rs similarity index 99% rename from src/modules/store/tantivy/attachment.rs rename to crates/core/src/store/tantivy/attachment.rs index e5ce6b8..0b6d967 100644 --- a/src/modules/store/tantivy/attachment.rs +++ b/crates/core/src/store/tantivy/attachment.rs @@ -25,28 +25,19 @@ use std::{ }; use crate::{ - modules::{ - common::signal::SIGNAL_MANAGER, - dashboard::{Group, LargestAttachment}, - error::{code::ErrorCode, BichonResult}, - message::{ + common::{paginated::DataPage, signal::SIGNAL_MANAGER}, dashboard::{Group, LargestAttachment}, error::{BichonResult, code::ErrorCode}, message::{ attachment::AttachmentMetadata, search::{AttachmentSearchFilter, SortBy}, tags::{TagAction, TagCount, TagsRequest}, - }, - rest::response::DataPage, - settings::dir::DATA_DIR_MANAGER, - store::tantivy::{ + }, raise_error, settings::dir::DATA_DIR_MANAGER, store::tantivy::{ fatal_commit, fields::{ F_ATTACHMENT_CATEGORY, F_ATTACHMENT_CONTENT_TYPE, F_ATTACHMENT_EXT, F_DATE, F_SIZE, F_TAGS, }, - model::{extract_senders, AttachmentModel}, + model::{AttachmentModel, extract_senders}, schema::SchemaTools, - }, - }, - raise_error, + } }; use serde_json::json; diff --git a/src/modules/store/tantivy/envelope.rs b/crates/core/src/store/tantivy/envelope.rs similarity index 99% rename from src/modules/store/tantivy/envelope.rs rename to crates/core/src/store/tantivy/envelope.rs index fd4e24f..69ce4a9 100644 --- a/src/modules/store/tantivy/envelope.rs +++ b/crates/core/src/store/tantivy/envelope.rs @@ -25,18 +25,10 @@ use std::{ }; use crate::{ - modules::{ - account::migration::AccountModel, - common::signal::SIGNAL_MANAGER, - dashboard::{DashboardStats, Group, LargestEmail, TimeBucket}, - error::{code::ErrorCode, BichonResult}, - message::{ + account::migration::AccountModel, common::{paginated::DataPage, signal::SIGNAL_MANAGER}, dashboard::{DashboardStats, Group, LargestEmail, TimeBucket}, error::{BichonResult, code::ErrorCode}, message::{ search::{EmailSearchFilter, SortBy}, tags::{TagAction, TagCount, TagsRequest}, - }, - rest::response::DataPage, - settings::dir::DATA_DIR_MANAGER, - store::{ + }, raise_error, settings::dir::DATA_DIR_MANAGER, store::{ envelope::Envelope, storage::BLOB_MANAGER, tantivy::{ @@ -45,12 +37,10 @@ use crate::{ F_ACCOUNT_ID, F_DATE, F_FROM, F_REGULAR_ATTACHMENT_COUNT, F_SIZE, F_TAGS, F_THREAD_ID, F_UID, }, - model::{extract_contacts, EnvelopeWithAttachments}, + model::{EnvelopeWithAttachments, extract_contacts}, schema::SchemaTools, }, - }, - }, - raise_error, utc_now, + }, utc_now }; use chrono::Utc; diff --git a/src/modules/store/tantivy/fields.rs b/crates/core/src/store/tantivy/fields.rs similarity index 100% rename from src/modules/store/tantivy/fields.rs rename to crates/core/src/store/tantivy/fields.rs diff --git a/src/modules/store/tantivy/mod.rs b/crates/core/src/store/tantivy/mod.rs similarity index 100% rename from src/modules/store/tantivy/mod.rs rename to crates/core/src/store/tantivy/mod.rs diff --git a/src/modules/store/tantivy/model.rs b/crates/core/src/store/tantivy/model.rs similarity index 99% rename from src/modules/store/tantivy/model.rs rename to crates/core/src/store/tantivy/model.rs index f943ada..cc74e28 100644 --- a/src/modules/store/tantivy/model.rs +++ b/crates/core/src/store/tantivy/model.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use tantivy::{ @@ -25,7 +25,8 @@ use tantivy::{ }; use crate::{ - modules::{ + raise_error, + { account::migration::AccountModel, cache::imap::mailbox::MailBox, error::{code::ErrorCode, BichonResult}, @@ -44,7 +45,6 @@ use crate::{ }, }, }, - raise_error, }; #[derive(Debug, Clone)] @@ -308,7 +308,8 @@ pub async fn extract_senders(doc: &TantivyDocument) -> BichonResult. -use crate::modules::common::periodic::TaskHandle; -use crate::modules::context::BichonTask; -use crate::modules::oauth2::{refresh::OAuth2RefreshTask, task::OAuth2CleanTask}; +use crate::common::periodic::TaskHandle; +use crate::context::BichonTask; +use crate::oauth2::{refresh::OAuth2RefreshTask, task::OAuth2CleanTask}; pub struct PeriodicTasks { tasks: Vec, } diff --git a/src/modules/token/mod.rs b/crates/core/src/token/mod.rs similarity index 94% rename from src/modules/token/mod.rs rename to crates/core/src/token/mod.rs index cc02ade..36199bf 100644 --- a/src/modules/token/mod.rs +++ b/crates/core/src/token/mod.rs @@ -19,22 +19,21 @@ use std::collections::HashMap; use super::error::code::ErrorCode; -use crate::modules::database::manager::DB_MANAGER; -use crate::modules::database::{ +use crate::database::manager::DB_MANAGER; +use crate::database::{ async_filter_by_secondary_key_impl, async_find_impl, delete_impl, with_transaction, }; -use crate::modules::database::{insert_impl, list_all_impl, update_impl}; -use crate::modules::settings::cli::SETTINGS; -use crate::modules::token::view::AccessTokenResp; -use crate::modules::users::UserModel; +use crate::database::{insert_impl, list_all_impl, update_impl}; use crate::raise_error; +use crate::settings::cli::SETTINGS; +use crate::token::view::AccessTokenResp; +use crate::users::UserModel; use crate::{ - generate_token, modules::error::BichonResult, - modules::token::payload::AccessTokenCreateRequest, utc_now, + error::BichonResult, generate_token, token::payload::AccessTokenCreateRequest, utc_now, }; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::{Enum, Object}; +//use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; pub mod payload; @@ -62,13 +61,15 @@ pub mod view; // pub acl: Option, // } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Enum)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum TokenType { WebUI, Api, } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 11, version = 1)] #[native_db] pub struct AccessTokenModel { diff --git a/src/modules/token/payload.rs b/crates/core/src/token/payload.rs similarity index 91% rename from src/modules/token/payload.rs rename to crates/core/src/token/payload.rs index 1c2e3a2..fdbed4f 100644 --- a/src/modules/token/payload.rs +++ b/crates/core/src/token/payload.rs @@ -17,16 +17,17 @@ // along with this program. If not, see . use crate::{ - modules::error::{code::ErrorCode, BichonResult}, + error::{code::ErrorCode, BichonResult}, raise_error, }; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccessTokenCreateRequest { /// An optional description of the token's purpose or usage. - #[oai(validator(max_length = "32"))] + //#[oai(validator(max_length = "32"))] pub name: Option, /// The expiration interval for this token, in hours. /// None means the token does not expire (this applies only to API tokens). diff --git a/src/modules/token/view.rs b/crates/core/src/token/view.rs similarity index 91% rename from src/modules/token/view.rs rename to crates/core/src/token/view.rs index 9732289..6fc6abe 100644 --- a/src/modules/token/view.rs +++ b/crates/core/src/token/view.rs @@ -16,12 +16,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - -use crate::modules::token::TokenType; -use poem_openapi::Object; +use crate::token::TokenType; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccessTokenResp { pub user_id: u64, pub token: String, @@ -38,7 +37,7 @@ pub struct AccessTokenResp { pub expire_at: Option, /// The timestamp (in milliseconds since epoch) when the token was last used. pub last_access_at: i64, - + pub user_name: String, pub user_email: String, } diff --git a/src/modules/users/acl.rs b/crates/core/src/users/acl.rs similarity index 86% rename from src/modules/users/acl.rs rename to crates/core/src/users/acl.rs index 46974b4..7c88fbc 100644 --- a/src/modules/users/acl.rs +++ b/crates/core/src/users/acl.rs @@ -18,12 +18,16 @@ use std::{collections::BTreeSet, net::IpAddr}; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use crate::{modules::error::{BichonResult, code::ErrorCode}, raise_error}; +use crate::{ + error::{code::ErrorCode, BichonResult}, + raise_error, +}; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct RateLimit { /// The time window in seconds for the rate limit. pub interval: u64, @@ -31,7 +35,8 @@ pub struct RateLimit { pub quota: u32, } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct AccessControl { /// An optional set of valid IPv4 or IPv6 addresses allowed to use the access token. pub ip_whitelist: Option>, diff --git a/src/modules/users/manager.rs b/crates/core/src/users/manager.rs similarity index 98% rename from src/modules/users/manager.rs rename to crates/core/src/users/manager.rs index d873d7d..5df10f9 100644 --- a/src/modules/users/manager.rs +++ b/crates/core/src/users/manager.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::{ +use crate::{ context::Initialize, error::BichonResult, users::{role::UserRole, UserModel}, diff --git a/src/modules/users/minimal.rs b/crates/core/src/users/minimal.rs similarity index 93% rename from src/modules/users/minimal.rs rename to crates/core/src/users/minimal.rs index 76bfce7..b10508b 100644 --- a/src/modules/users/minimal.rs +++ b/crates/core/src/users/minimal.rs @@ -16,16 +16,17 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use crate::modules::{ +use crate::{ database::{list_all_impl, manager::DB_MANAGER}, error::BichonResult, users::UserModel, }; -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct MinimalUser { pub id: u64, pub username: String, diff --git a/src/modules/users/mod.rs b/crates/core/src/users/mod.rs similarity index 99% rename from src/modules/users/mod.rs rename to crates/core/src/users/mod.rs index 0b625e2..e523b35 100644 --- a/src/modules/users/mod.rs +++ b/crates/core/src/users/mod.rs @@ -17,8 +17,8 @@ // along with this program. If not, see . use crate::{ - decrypt, encrypt, generate_token, id, - modules::{ + decrypt, encrypt, generate_token, id, raise_error, utc_now, + { database::{ async_find_impl, async_secondary_find_impl, batch_delete_impl, delete_impl, list_all_impl, manager::DB_MANAGER, update_impl, with_transaction, @@ -33,12 +33,11 @@ use crate::{ view::UserView, }, }, - raise_error, utc_now, }; use itertools::Itertools; use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashSet}; use tracing::warn; @@ -53,7 +52,7 @@ pub mod view; pub type UserModel = BichonUserV2; -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct LoginResult { pub success: bool, pub error_message: Option, @@ -64,7 +63,7 @@ pub struct LoginResult { pub const DEFAULT_ADMIN_USER_ID: u64 = 100000000000000; -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[native_model(id = 10, version = 1)] #[native_db] pub struct BichonUser { @@ -96,7 +95,7 @@ pub struct BichonUser { pub acl: Option, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[native_model(id = 10, version = 2, from = BichonUser)] #[native_db] pub struct BichonUserV2 { diff --git a/src/modules/users/payload.rs b/crates/core/src/users/payload.rs similarity index 96% rename from src/modules/users/payload.rs rename to crates/core/src/users/payload.rs index d73ea8d..100f01f 100644 --- a/src/modules/users/payload.rs +++ b/crates/core/src/users/payload.rs @@ -17,7 +17,8 @@ // along with this program. If not, see . use crate::{ - modules::{ + raise_error, + { account::migration::AccountModel, error::{code::ErrorCode, BichonResult}, users::{ @@ -27,9 +28,8 @@ use crate::{ }, utils::decode_avatar_bytes, }, - raise_error, }; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; @@ -70,7 +70,8 @@ fn validate_language(language: &Option) -> BichonResult<()> { validate_option_in_set(language, &allowed_languages(), "language") } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct RoleCreateRequest { pub name: String, pub role_type: RoleType, @@ -123,7 +124,8 @@ impl RoleCreateRequest { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct RoleUpdateRequest { pub name: Option, pub description: Option, @@ -194,11 +196,12 @@ impl RoleUpdateRequest { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct UserCreateRequest { pub username: String, - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: String, pub password: String, @@ -331,16 +334,17 @@ impl UserCreateRequest { } } -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct UserUpdateRequest { pub username: Option, - #[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))] + //#[oai(validator(custom = "crate::common::validator::EmailValidator"))] pub email: Option, pub password: Option, pub avatar_base64: Option, pub global_roles: Option>, /// Scoped Access - pub account_access_map: Option>, + pub account_access_map: Option>, pub acl: Option, pub description: Option, pub theme: Option, diff --git a/src/modules/users/permissions.rs b/crates/core/src/users/permissions.rs similarity index 99% rename from src/modules/users/permissions.rs rename to crates/core/src/users/permissions.rs index 6cc71f2..2362ab3 100644 --- a/src/modules/users/permissions.rs +++ b/crates/core/src/users/permissions.rs @@ -22,11 +22,11 @@ use std::{ }; use crate::{ - modules::{ + raise_error, + { error::{code::ErrorCode, BichonResult}, users::role::RoleType, }, - raise_error, }; pub static VALID_PERMISSION_SET: LazyLock> = LazyLock::new(|| { diff --git a/src/modules/users/role.rs b/crates/core/src/users/role.rs similarity index 98% rename from src/modules/users/role.rs rename to crates/core/src/users/role.rs index b5944df..f28e380 100644 --- a/src/modules/users/role.rs +++ b/crates/core/src/users/role.rs @@ -23,12 +23,12 @@ use std::{ use native_db::*; use native_model::{native_model, Model}; -use poem_openapi::{Enum, Object}; +//use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; use crate::{ - id, - modules::{ + id, raise_error, utc_now, + { database::{ async_find_impl, delete_impl, insert_impl, list_all_impl, manager::DB_MANAGER, update_impl, with_transaction, @@ -40,7 +40,6 @@ use crate::{ UserModel, }, }, - raise_error, utc_now, }; /// Enumerates the built-in roles in the Bichon system. @@ -175,14 +174,16 @@ fn is_builtin(id: u64) -> bool { ) } -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Enum)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))] pub enum RoleType { #[default] Global, Account, } -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] #[native_model(id = 9, version = 1)] #[native_db] pub struct UserRole { diff --git a/src/modules/users/view.rs b/crates/core/src/users/view.rs similarity index 93% rename from src/modules/users/view.rs rename to crates/core/src/users/view.rs index 70b672f..2839635 100644 --- a/src/modules/users/view.rs +++ b/crates/core/src/users/view.rs @@ -18,12 +18,13 @@ use std::collections::BTreeMap; -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use crate::modules::users::acl::AccessControl; +use crate::users::acl::AccessControl; -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct UserView { pub id: u64, pub username: String, diff --git a/src/modules/utils/encrypt.rs b/crates/core/src/utils/encrypt.rs similarity index 97% rename from src/modules/utils/encrypt.rs rename to crates/core/src/utils/encrypt.rs index c7918c0..5eb9533 100644 --- a/src/modules/utils/encrypt.rs +++ b/crates/core/src/utils/encrypt.rs @@ -24,9 +24,9 @@ use std::fs; use std::num::NonZeroU32; use std::sync::LazyLock; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::BichonResult; -use crate::modules::settings::cli::SETTINGS; +use crate::error::code::ErrorCode; +use crate::error::BichonResult; +use crate::settings::cli::SETTINGS; use crate::raise_error; static ENCRYPT_PASSWORD: LazyLock = LazyLock::new(|| { diff --git a/src/modules/utils/html.rs b/crates/core/src/utils/html.rs similarity index 100% rename from src/modules/utils/html.rs rename to crates/core/src/utils/html.rs diff --git a/src/modules/utils/mod.rs b/crates/core/src/utils/mod.rs similarity index 93% rename from src/modules/utils/mod.rs rename to crates/core/src/utils/mod.rs index dc74d02..3399ef2 100644 --- a/src/modules/utils/mod.rs +++ b/crates/core/src/utils/mod.rs @@ -18,7 +18,7 @@ use std::{fs, io, path::PathBuf}; -use crate::modules::error::BichonResult; +use crate::error::BichonResult; use base64::engine::general_purpose::STANDARD; use base64::{engine::general_purpose, Engine}; use rand::{rng, RngExt}; @@ -104,7 +104,7 @@ macro_rules! license_header { #[macro_export] macro_rules! raise_error { ($msg:expr, $code:expr) => { - $crate::modules::error::BichonError::Generic { + $crate::error::BichonError::Generic { message: $msg, code: $code, location: snafu::location!(), @@ -134,7 +134,7 @@ macro_rules! free_memory { #[macro_export] macro_rules! generate_token { ($bit_strength:expr) => {{ - $crate::modules::utils::generate_token_impl($bit_strength) + $crate::utils::generate_token_impl($bit_strength) }}; } @@ -169,7 +169,7 @@ fn make_single_random_char() -> char { #[macro_export] macro_rules! ensure_access { ($dir:expr) => {{ - $crate::modules::utils::ensure_dir_and_test_access($dir) + $crate::utils::ensure_dir_and_test_access($dir) }}; } @@ -208,25 +208,25 @@ macro_rules! current_datetime { #[macro_export] macro_rules! validate_email { ($email:expr) => {{ - $crate::modules::utils::validate_email($email) + $crate::utils::validate_email($email) }}; } #[macro_export] macro_rules! encrypt { ($plaintext:expr) => {{ - $crate::modules::utils::encrypt::encrypt_string($plaintext) + $crate::utils::encrypt::encrypt_string($plaintext) }}; } #[macro_export] macro_rules! decrypt { ($plaintext:expr) => {{ - $crate::modules::utils::encrypt::decrypt_string($plaintext) + $crate::utils::encrypt::decrypt_string($plaintext) }}; } -pub fn validate_email(email: &str) -> crate::modules::error::BichonResult<()> { +pub fn validate_email(email: &str) -> crate::error::BichonResult<()> { use std::str::FromStr; let email_address = email_address::EmailAddress::from_str(email).map_err(|_| { raise_error!( @@ -247,9 +247,9 @@ pub fn validate_email(email: &str) -> crate::modules::error::BichonResult<()> { macro_rules! id { ($bit_strength:expr) => {{ // Generate a token with the given bit strength - let token = $crate::modules::utils::generate_token_impl($bit_strength); + let token = $crate::utils::generate_token_impl($bit_strength); // Hash the generated token - $crate::modules::utils::hash(&token) + $crate::utils::hash(&token) }}; } diff --git a/src/modules/utils/net.rs b/crates/core/src/utils/net.rs similarity index 96% rename from src/modules/utils/net.rs rename to crates/core/src/utils/net.rs index 73914d1..31f8543 100644 --- a/src/modules/utils/net.rs +++ b/crates/core/src/utils/net.rs @@ -16,11 +16,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::error::code::ErrorCode; -use crate::modules::settings::proxy::Proxy; -use crate::modules::utils::tls::establish_tls_stream; -use crate::modules::{error::BichonResult, imap::session::SessionStream}; +use crate::error::code::ErrorCode; use crate::raise_error; +use crate::settings::proxy::Proxy; +use crate::utils::tls::establish_tls_stream; +use crate::{error::BichonResult, imap::session::SessionStream}; use std::net::SocketAddr; use std::pin::Pin; use std::time::Duration; diff --git a/src/modules/utils/rate_limit/mod.rs b/crates/core/src/utils/rate_limit/mod.rs similarity index 98% rename from src/modules/utils/rate_limit/mod.rs rename to crates/core/src/utils/rate_limit/mod.rs index 7768fa6..bdea0bf 100644 --- a/src/modules/utils/rate_limit/mod.rs +++ b/crates/core/src/utils/rate_limit/mod.rs @@ -29,7 +29,7 @@ use std::{ time::Duration, }; -use crate::modules::users::acl::RateLimit; +use crate::users::acl::RateLimit; pub static RATE_LIMITER_MANAGER: LazyLock = LazyLock::new(UserRateLimiter::new); diff --git a/src/modules/utils/shutdown.rs b/crates/core/src/utils/shutdown.rs similarity index 100% rename from src/modules/utils/shutdown.rs rename to crates/core/src/utils/shutdown.rs diff --git a/src/modules/utils/tls.rs b/crates/core/src/utils/tls.rs similarity index 99% rename from src/modules/utils/tls.rs rename to crates/core/src/utils/tls.rs index 39fd1fb..061c24b 100644 --- a/src/modules/utils/tls.rs +++ b/crates/core/src/utils/tls.rs @@ -17,11 +17,11 @@ // along with this program. If not, see . use crate::{ - modules::{ + raise_error, + { error::{code::ErrorCode, BichonResult}, imap::session::SessionStream, }, - raise_error, }; use rustls::{ client::danger::{ServerCertVerified, ServerCertVerifier}, diff --git a/src/modules/version/mod.rs b/crates/core/src/version/mod.rs similarity index 89% rename from src/modules/version/mod.rs rename to crates/core/src/version/mod.rs index f188c90..8400107 100644 --- a/src/modules/version/mod.rs +++ b/crates/core/src/version/mod.rs @@ -16,13 +16,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - -use poem_openapi::Object; +//use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use crate::{modules::error::BichonResult, bichon_version}; +use crate::{bichon_version, error::BichonResult}; -#[derive(Debug, Serialize, Deserialize, Object)] +#[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct ReleaseNotification { /// Details of the latest release, if available. `None` if no release data is available. pub latest: Option, @@ -32,7 +32,8 @@ pub struct ReleaseNotification { pub error_message: Option, } -#[derive(Debug, Serialize, Deserialize, Object)] +#[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct Release { /// The tag name of the release (e.g., "v1.2.3"). pub tag_name: String, @@ -44,7 +45,8 @@ pub struct Release { pub html_url: String, } -#[derive(Debug, Serialize, Deserialize, Object)] +#[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))] pub struct Notifications { pub release: ReleaseNotification, } @@ -105,7 +107,7 @@ async fn check_new_release(owner: &str, repo: &str, current_version: &str) -> Re #[cfg(test)] mod test { - use crate::{modules::version::check_new_release, bichon_version}; + use crate::{bichon_version, version::check_new_release}; #[tokio::test] async fn test() { diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml new file mode 100644 index 0000000..a2105ff --- /dev/null +++ b/crates/server/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "bichon-server" +version.workspace = true +edition.workspace = true + + +[dependencies] +bichon-core = { path = "../core", features = ["web-api"] } +bichon-smtp = { path = "../smtp" } +poem = { version = "3.1.12", features = ["embed", "compression", "rustls"] } +poem-derive = "3.1.12" +poem-openapi = { version = "5.1.16", features = [ + "openapi-explorer", + "rapidoc", + "scalar", + "redoc", + "swagger-ui", + "email", +] } +rust-embed = "8.11.0" +email_address.workspace = true +tantivy.workspace = true +serde.workspace = true +serde_json.workspace = true +governor.workspace = true +snafu.workspace = true +timeago.workspace = true +chrono.workspace = true +tracing.workspace = true +tokio.workspace = true +http.workspace = true +urlencoding.workspace = true +mimalloc.workspace = true diff --git a/build.rs b/crates/server/build.rs similarity index 100% rename from build.rs rename to crates/server/build.rs diff --git a/crates/server/src/common/auth.rs b/crates/server/src/common/auth.rs new file mode 100644 index 0000000..bf74e5d --- /dev/null +++ b/crates/server/src/common/auth.rs @@ -0,0 +1,155 @@ +// +// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) +// +// This file is part of the Bichon Email Archiving Project +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use bichon_core::{ + common::auth::ClientContext, error::code::ErrorCode, token::AccessTokenModel, + utils::rate_limit::RATE_LIMITER_MANAGER, +}; +use governor::clock::{Clock, QuantaClock}; +use poem::{ + web::{ + headers::{authorization::Bearer, Authorization, HeaderMapExt}, + RealIp, + }, + Endpoint, FromRequest, Middleware, Request, RequestBody, Result, +}; +use serde::Deserialize; +use std::{ops::Deref, sync::Arc}; + +use super::create_api_error_response; + +pub struct ApiGuard; + +pub struct ApiGuardEndpoint { + ep: E, +} + +impl Middleware for ApiGuard { + type Output = ApiGuardEndpoint; + + fn transform(&self, ep: E) -> Self::Output { + ApiGuardEndpoint { ep } + } +} + +#[derive(Deserialize)] +struct Param { + access_token: String, +} + +impl Endpoint for ApiGuardEndpoint { + type Output = E::Output; + + async fn call(&self, mut req: Request) -> Result { + let context = authorize_access(&req).await?; + req.set_data(Arc::new(context)); + self.ep.call(req).await + } +} + +pub struct WrappedContext(pub ClientContext); + +impl Deref for WrappedContext { + type Target = ClientContext; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> FromRequest<'a> for WrappedContext { + async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result { + let ctx = extract_client_context(req).await?; + Ok(WrappedContext(ctx)) + } +} + +pub async fn extract_client_context(req: &Request) -> Result { + let ip_addr = RealIp::from_request_without_body(req) + .await + .map_err(|_| { + create_api_error_response( + "Failed to parse client IP address", + ErrorCode::InvalidParameter, + ) + })? + .0 + .ok_or_else(|| { + create_api_error_response( + "Failed to parse client IP address", + ErrorCode::InvalidParameter, + ) + })?; + // Extract access token from Bearer header or query params + let bearer = req + .headers() + .typed_get::>() + .map(|auth| auth.0.token().to_string()) + .or_else(|| req.params::().ok().map(|param| param.access_token)); + + let token = bearer.ok_or_else(|| { + create_api_error_response("Valid access token not found", ErrorCode::PermissionDenied) + })?; + + // Validate and update access token + let user = AccessTokenModel::resolve_user_from_token(&token) + .await + .map_err(|e| { + create_api_error_response(&format!("{:#?}", e), ErrorCode::PermissionDenied) + })?; + + return Ok(ClientContext { + ip_addr: Some(ip_addr), + user, + }); +} + +pub async fn authorize_access(req: &Request) -> Result { + let context = extract_client_context(&req).await?; + if let Some(access_control) = &context.user.acl { + if let Some(ip_addr) = context.ip_addr { + if let Some(whitelist) = &access_control.ip_whitelist { + if !whitelist.contains(&ip_addr.to_string()) { + return Err(create_api_error_response( + &format!("IP {} not in whitelist", ip_addr), + ErrorCode::Forbidden, + )); + } + } + } + + if let Some(rate_limit) = &access_control.rate_limit { + if let Err(not_until) = RATE_LIMITER_MANAGER + .check(context.user.id, rate_limit.clone()) + .await + { + let wait_duration = not_until.wait_time_from(QuantaClock::default().now()); + return Err(create_api_error_response( + &format!( + "Rate limit: {}/{}s. Retry after {}s", + rate_limit.quota, + rate_limit.interval, + wait_duration.as_secs() + ), + ErrorCode::TooManyRequest, + )); + } + } + } + + Ok(context) +} diff --git a/src/modules/common/error.rs b/crates/server/src/common/error.rs similarity index 96% rename from src/modules/common/error.rs rename to crates/server/src/common/error.rs index 2553641..5cec0b0 100644 --- a/src/modules/common/error.rs +++ b/crates/server/src/common/error.rs @@ -19,7 +19,7 @@ use poem::{Endpoint, IntoResponse, Middleware, Request, Response, Result}; -use crate::modules::error::handler::error_handler; +use crate::error::handler::error_handler; pub struct ErrorCapture; diff --git a/src/modules/common/log.rs b/crates/server/src/common/log.rs similarity index 100% rename from src/modules/common/log.rs rename to crates/server/src/common/log.rs diff --git a/crates/server/src/common/mod.rs b/crates/server/src/common/mod.rs new file mode 100644 index 0000000..7119cff --- /dev/null +++ b/crates/server/src/common/mod.rs @@ -0,0 +1,108 @@ +// +// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) +// +// This file is part of the Bichon Email Archiving Project +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::fmt; + +use bichon_core::error::code::ErrorCode; +use bichon_core::error::BichonError; +use poem::error::ResponseError; +use poem::Body; +use poem::{http::StatusCode, Error, Response}; +use tracing::error; + +use crate::error::code::IntoStatusCode; + +pub mod auth; +pub mod error; +pub mod log; +pub mod status; +pub mod timeout; +pub mod tls; +pub mod validator; + +#[derive(Debug)] +pub struct BichonServerError(pub BichonError); + +impl fmt::Display for BichonServerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Internal Server Error: {:?}", self.0) + } +} + +impl std::error::Error for BichonServerError {} + +impl From for BichonServerError { + fn from(err: BichonError) -> Self { + BichonServerError(err) + } +} + +#[inline] +fn create_rust_mailer_error(message: &str, code: ErrorCode) -> BichonServerError { + BichonError::Generic { + message: message.into(), + location: snafu::location!(), + code, + } + .into() +} + +#[inline] +pub fn create_api_error_response(message: &str, code: ErrorCode) -> Error { + let rust_mailer_error = create_rust_mailer_error(message, code); + rust_mailer_error.into() +} + +impl ResponseError for BichonServerError { + fn status(&self) -> StatusCode { + match self.0 { + BichonError::Generic { + message: _, + location: _, + code, + } => code.status(), + } + } + + fn as_response(&self) -> Response + where + Self: std::error::Error + Send + Sync + 'static, + { + match &self.0 { + BichonError::Generic { + message, + location, + code, + } => { + error!( + error_code = code.to_u32(), + error_message = %message, + error_location = ?location + ); + + let body = Body::from_json(serde_json::json!({ + "code": code.to_u32(), + "message": message.to_string(), + })) + .unwrap(); + + Response::builder().status(self.status()).body(body) + } + } + } +} diff --git a/src/modules/context/status.rs b/crates/server/src/common/status.rs similarity index 97% rename from src/modules/context/status.rs rename to crates/server/src/common/status.rs index 0a7ff85..7013418 100644 --- a/src/modules/context/status.rs +++ b/crates/server/src/common/status.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . -use crate::modules::context::executors::BICHON_CONTEXT; +use bichon_core::context::executors::BICHON_CONTEXT; use chrono::Local; use poem_openapi::Object; use serde::Deserialize; diff --git a/src/modules/common/timeout.rs b/crates/server/src/common/timeout.rs similarity index 98% rename from src/modules/common/timeout.rs rename to crates/server/src/common/timeout.rs index 6ff2446..5448dcd 100644 --- a/src/modules/common/timeout.rs +++ b/crates/server/src/common/timeout.rs @@ -16,13 +16,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - +use bichon_core::error::code::ErrorCode; use poem::{Endpoint, Middleware, Request, Result}; use std::time::Duration; use tracing::error; -use crate::modules::error::code::ErrorCode; - use super::create_api_error_response; pub const TIMEOUT_HEADER: &str = "X-Bichon-Timeout-Seconds"; diff --git a/src/modules/common/tls.rs b/crates/server/src/common/tls.rs similarity index 98% rename from src/modules/common/tls.rs rename to crates/server/src/common/tls.rs index 1577024..f3fbc17 100644 --- a/src/modules/common/tls.rs +++ b/crates/server/src/common/tls.rs @@ -16,15 +16,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use poem::listener::{RustlsCertificate, RustlsConfig}; - -use crate::{ - modules::{ +use bichon_core::{ + raise_error, + { error::{code::ErrorCode, BichonResult}, settings::dir::DATA_DIR_MANAGER, }, - raise_error, }; pub fn rustls_config() -> BichonResult { diff --git a/src/modules/common/validator.rs b/crates/server/src/common/validator.rs similarity index 100% rename from src/modules/common/validator.rs rename to crates/server/src/common/validator.rs diff --git a/src/modules/error/code.rs b/crates/server/src/error/code.rs similarity index 64% rename from src/modules/error/code.rs rename to crates/server/src/error/code.rs index a121ca5..a153d0b 100644 --- a/src/modules/error/code.rs +++ b/crates/server/src/error/code.rs @@ -16,50 +16,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use bichon_core::error::code::ErrorCode; use poem::http::StatusCode; -use poem_openapi::Enum; -#[derive(Copy, Clone, Debug, Enum, Eq, PartialEq)] -#[repr(u32)] -pub enum ErrorCode { - // Client-side errors (10000–10999) - InvalidParameter = 10000, - MissingConfiguration = 10020, - Incompatible = 10030, - PayloadTooLarge = 10070, - RequestTimeout = 10080, - MethodNotAllowed = 10090, - - // Authentication and authorization errors (20000–20999) - PermissionDenied = 20000, - AccountDisabled = 20010, - Forbidden = 20020, - OAuth2ItemDisabled = 20050, - MissingRefreshToken = 20060, - - // Resource errors (30000–30999) - ResourceNotFound = 30000, - TooManyRequest = 30020, - AlreadyExists = 30030, - - // Network connection errors (40000–40999) - NetworkError = 40000, - ConnectionTimeout = 40010, - ConnectionPoolTimeout = 40020, - HttpResponseError = 40030, - - // Mail service errors (50000–50999) - ImapCommandFailed = 50000, - ImapAuthenticationFailed = 50010, - ImapUnexpectedResult = 50020, - AutoconfigFetchFailed = 50060, - // Internal system errors (70000–70999) - InternalError = 70000, - UnhandledPoemError = 70010, +pub trait IntoStatusCode { + fn status(&self) -> StatusCode; } -impl ErrorCode { - pub fn status(&self) -> StatusCode { +impl IntoStatusCode for ErrorCode { + fn status(&self) -> StatusCode { match self { ErrorCode::InvalidParameter | ErrorCode::MissingConfiguration diff --git a/src/modules/error/handler.rs b/crates/server/src/error/handler.rs similarity index 95% rename from src/modules/error/handler.rs rename to crates/server/src/error/handler.rs index ad0d412..7f65c24 100644 --- a/src/modules/error/handler.rs +++ b/crates/server/src/error/handler.rs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - -use crate::modules::error::{code::ErrorCode, ApiError, ApiErrorResponse, BichonError}; +use bichon_core::error::{code::ErrorCode, BichonError}; use poem::IntoResponse; use poem_openapi::payload::Json; +use crate::error::{ApiError, ApiErrorResponse, code::IntoStatusCode}; + pub async fn error_handler(error: poem::Error) -> impl poem::IntoResponse { if error.is::() { return error.into_response(); diff --git a/src/modules/error/mod.rs b/crates/server/src/error/mod.rs similarity index 86% rename from src/modules/error/mod.rs rename to crates/server/src/error/mod.rs index ba1da01..c035eb0 100644 --- a/src/modules/error/mod.rs +++ b/crates/server/src/error/mod.rs @@ -16,29 +16,15 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use code::ErrorCode; +use crate::error::code::IntoStatusCode; +use bichon_core::error::BichonError; use poem::http::StatusCode; use poem_openapi::{payload::Json, ApiResponse, Object}; -use snafu::{Location, Snafu}; use std::{fmt::Formatter, u32}; pub mod code; pub mod handler; -#[derive(Debug, Snafu)] -#[snafu(visibility(pub))] -pub enum BichonError { - #[snafu(display("{message}"))] - Generic { - message: String, - #[snafu(implicit)] - location: Location, - code: ErrorCode, - }, -} - -pub type BichonResult = std::result::Result; - #[derive(Debug, Clone, Object)] pub struct ApiError { pub message: String, diff --git a/src/main.rs b/crates/server/src/main.rs similarity index 87% rename from src/main.rs rename to crates/server/src/main.rs index b3ad707..0295905 100644 --- a/src/main.rs +++ b/crates/server/src/main.rs @@ -18,29 +18,36 @@ use std::sync::LazyLock; -use bichon::{ - bichon_version, - modules::{ +use bichon_core::{ + bichon_version, raise_error, + { cache::imap::task::SYNC_TASKS, common::rustls::BichonTls, - context::{Initialize, executors::BichonContext}, - error::{BichonResult, code::ErrorCode}, + context::{executors::BichonContext, Initialize}, + error::{code::ErrorCode, BichonResult}, logger, - rest::start_http_server, settings::cli::SETTINGS, - smtp::{SmtpServer, start_smtp_server}, - store::{storage::BLOB_MANAGER, tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER}}, + store::{ + storage::BLOB_MANAGER, + tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER}, + }, tasks::PeriodicTasks, }, - raise_error, }; +use bichon_smtp::server::{start_smtp_server, SmtpServer}; use mimalloc::MiMalloc; use tracing::{error, info}; -use bichon::modules::{ +use bichon_core::{ common::signal::SignalManager, settings::dir::DataDirManager, users::manager::UserManager, }; +use crate::rest::start_http_server; + +pub mod common; +pub mod error; +pub mod rest; + #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; diff --git a/src/modules/rest/api/access_token.rs b/crates/server/src/rest/api/access_token.rs similarity index 87% rename from src/modules/rest/api/access_token.rs rename to crates/server/src/rest/api/access_token.rs index ad239c7..7c3ed6f 100644 --- a/src/modules/rest/api/access_token.rs +++ b/crates/server/src/rest/api/access_token.rs @@ -16,12 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::auth::ClientContext; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::ApiResult; -use crate::modules::token::view::AccessTokenResp; -use crate::modules::users::permissions::Permission; -use crate::modules::{token::payload::AccessTokenCreateRequest, token::AccessTokenModel}; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::token::view::AccessTokenResp; +use bichon_core::users::permissions::Permission; +use bichon_core::{token::payload::AccessTokenCreateRequest, token::AccessTokenModel}; use poem_openapi::payload::PlainText; use poem_openapi::{param::Path, payload::Json, OpenApi}; @@ -36,7 +36,7 @@ impl AccessTokenApi { )] async fn list_access_tokens( &self, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { context .require_permission(None, Permission::TOKEN_MANAGE) @@ -55,7 +55,7 @@ impl AccessTokenApi { &self, /// The access token to be deleted token: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let token = token.0.trim(); let token = AccessTokenModel::get_token(token).await?; @@ -76,7 +76,7 @@ impl AccessTokenApi { )] async fn create_access_token( &self, - context: ClientContext, + context: WrappedContext, /// The request payload payload: Json, ) -> ApiResult> { diff --git a/src/modules/rest/api/account.rs b/crates/server/src/rest/api/account.rs similarity index 90% rename from src/modules/rest/api/account.rs rename to crates/server/src/rest/api/account.rs index 033d94f..99a74df 100644 --- a/src/modules/rest/api/account.rs +++ b/crates/server/src/rest/api/account.rs @@ -16,25 +16,23 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::collections::{HashMap, HashSet}; - -use crate::modules::account::grant::BatchAccountRoleRequest; -use crate::modules::account::migration::AccountModel; -use crate::modules::account::payload::{ +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::account::grant::BatchAccountRoleRequest; +use bichon_core::account::migration::AccountModel; +use bichon_core::account::payload::{ filter_accessible_accounts, AccountCreateRequest, AccountUpdateRequest, MinimalAccount, }; -use crate::modules::account::state::DownloadState; -use crate::modules::account::view::AccountResp; -use crate::modules::common::auth::ClientContext; -use crate::modules::common::paginated::paginate_vec; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::response::DataPage; -use crate::modules::rest::ApiResult; -use crate::modules::users::permissions::Permission; -use crate::modules::users::UserModel; +use bichon_core::account::state::DownloadState; +use bichon_core::account::view::AccountResp; +use bichon_core::common::paginated::{paginate_vec, DataPage}; +use bichon_core::users::permissions::Permission; +use bichon_core::users::UserModel; use poem_openapi::param::{Path, Query}; use poem_openapi::payload::Json; use poem_openapi::OpenApi; +use std::collections::{HashMap, HashSet}; pub struct AccountApi; @@ -50,7 +48,7 @@ impl AccountApi { &self, /// The account ID to retrieve account_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; context @@ -69,7 +67,7 @@ impl AccountApi { &self, /// The account ID to delete account_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let account_id = account_id.0; context @@ -84,7 +82,7 @@ impl AccountApi { &self, /// Account creation request payload payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context .require_permission(None, Permission::ACCOUNT_CREATE) @@ -105,7 +103,7 @@ impl AccountApi { account_id: Path, /// Account update request payload payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let account_id = account_id.0; context @@ -124,7 +122,7 @@ impl AccountApi { page_size: Query>, /// Optional. Whether to sort the list in descending order. desc: Query>, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let is_admin = context.user.is_admin().await; let sort_desc = desc.0.unwrap_or(true); @@ -192,7 +190,7 @@ impl AccountApi { &self, /// The account ID to check state for account_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; AccountModel::check_account_exists(account_id).await?; @@ -216,7 +214,7 @@ impl AccountApi { async fn minimal_accounts_list( &self, only_nosync: Query>, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let is_admin = context.user.is_admin().await; let only_nosync = only_nosync.0.unwrap_or_default(); @@ -235,7 +233,7 @@ impl AccountApi { async fn batch_assign_account_role( &self, req: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { req.validate_existence().await?; req.0.do_assign(&context).await?; diff --git a/src/modules/rest/api/attachment.rs b/crates/server/src/rest/api/attachment.rs similarity index 86% rename from src/modules/rest/api/attachment.rs rename to crates/server/src/rest/api/attachment.rs index 2a8e31e..0efbcb6 100644 --- a/src/modules/rest/api/attachment.rs +++ b/crates/server/src/rest/api/attachment.rs @@ -16,25 +16,25 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::auth::ClientContext; -use crate::modules::message::attachment::AttachmentMetadata; -use crate::modules::message::search::search_attachment_impl; -use crate::modules::message::search::AttachmentSearchRequest; -use crate::modules::message::tags::TagCount; -use crate::modules::message::tags::TagsRequest; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::response::DataPage; -use crate::modules::rest::ApiResult; -use crate::modules::rest::ErrorCode; -use crate::modules::store::tantivy::attachment::ATTACHMENT_MANAGER; -use crate::modules::store::tantivy::model::AttachmentModel; -use crate::modules::users::permissions::Permission; -use crate::raise_error; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::common::paginated::DataPage; +use bichon_core::error::code::ErrorCode; +use bichon_core::message::attachment::AttachmentMetadata; +use bichon_core::message::search::search_attachment_impl; +use bichon_core::message::search::AttachmentSearchRequest; +use bichon_core::message::tags::TagCount; +use bichon_core::message::tags::TagsRequest; +use bichon_core::raise_error; +use bichon_core::store::tantivy::attachment::ATTACHMENT_MANAGER; +use bichon_core::store::tantivy::model::AttachmentModel; +use bichon_core::users::permissions::Permission; use poem_openapi::param::Path; use poem_openapi::payload::Json; use poem_openapi::OpenApi; -use tantivy::schema::Facet; use std::collections::HashSet; +use tantivy::schema::Facet; pub struct AttachmentApi; @@ -50,7 +50,7 @@ impl AttachmentApi { async fn search_attachment( &self, payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) @@ -77,7 +77,7 @@ impl AttachmentApi { account_id: Path, /// The ID of the attachment. attachment_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; context @@ -107,7 +107,7 @@ impl AttachmentApi { )] async fn get_all_attachment_tags( &self, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) @@ -129,7 +129,7 @@ impl AttachmentApi { async fn update_attachment_tags( &self, req: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let req = req.0; for tag in &req.tags { @@ -155,7 +155,7 @@ impl AttachmentApi { )] async fn get_attachment_senders( &self, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) @@ -178,7 +178,7 @@ impl AttachmentApi { )] async fn get_attachment_metadata( &self, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) diff --git a/src/modules/rest/api/auto_config.rs b/crates/server/src/rest/api/auto_config.rs similarity index 82% rename from src/modules/rest/api/auto_config.rs rename to crates/server/src/rest/api/auto_config.rs index 2a3ec08..5e430e5 100644 --- a/src/modules/rest/api/auto_config.rs +++ b/crates/server/src/rest/api/auto_config.rs @@ -16,14 +16,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::autoconfig::entity::MailServerConfig; -use crate::modules::autoconfig::load::resolve_autoconfig; -use crate::modules::common::auth::ClientContext; -use crate::modules::error::code::ErrorCode; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::ApiResult; -use crate::modules::users::permissions::Permission; -use crate::raise_error; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::autoconfig::entity::MailServerConfig; +use bichon_core::autoconfig::load::resolve_autoconfig; +use bichon_core::error::code::ErrorCode; +use bichon_core::raise_error; +use bichon_core::users::permissions::Permission; use poem_openapi::param::Path; use poem_openapi::payload::Json; use poem_openapi::OpenApi; @@ -42,7 +42,7 @@ impl AutoConfigApi { &self, /// The email address to lookup configuration for email_address: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context .require_permission(None, Permission::ACCOUNT_CREATE) diff --git a/src/modules/rest/api/import.rs b/crates/server/src/rest/api/import.rs similarity index 85% rename from src/modules/rest/api/import.rs rename to crates/server/src/rest/api/import.rs index 0e81585..9067fa1 100644 --- a/src/modules/rest/api/import.rs +++ b/crates/server/src/rest/api/import.rs @@ -16,12 +16,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::auth::ClientContext; -use crate::modules::import::BatchEmlResult; -use crate::modules::import::{BatchEmlRequest, ImportEmls}; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::ApiResult; -use crate::modules::users::permissions::Permission; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::import::BatchEmlResult; +use bichon_core::import::{BatchEmlRequest, ImportEmls}; +use bichon_core::users::permissions::Permission; use poem_openapi::payload::Json; use poem_openapi::OpenApi; @@ -42,7 +42,7 @@ impl ImportApi { &self, /// JSON payload with account info and EML files to import payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context .require_permission(Some(payload.0.account_id), Permission::DATA_IMPORT_BATCH) diff --git a/src/modules/rest/api/mailbox.rs b/crates/server/src/rest/api/mailbox.rs similarity index 87% rename from src/modules/rest/api/mailbox.rs rename to crates/server/src/rest/api/mailbox.rs index 0d93d00..b0375a5 100644 --- a/src/modules/rest/api/mailbox.rs +++ b/crates/server/src/rest/api/mailbox.rs @@ -16,13 +16,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::cache::imap::mailbox::MailBox; -use crate::modules::common::auth::ClientContext; -use crate::modules::mailbox::delete::delete_mailbox_impl; -use crate::modules::mailbox::list::get_account_mailboxes; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::ApiResult; -use crate::modules::users::permissions::Permission; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::cache::imap::mailbox::MailBox; +use bichon_core::mailbox::delete::delete_mailbox_impl; +use bichon_core::mailbox::list::get_account_mailboxes; +use bichon_core::users::permissions::Permission; use poem_openapi::param::{Path, Query}; use poem_openapi::payload::Json; use poem_openapi::OpenApi; @@ -50,7 +50,7 @@ impl MailBoxApi { /// The unique identifier of the account. account_id: Path, remote: Query>, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let account_id = account_id.0; context @@ -78,7 +78,7 @@ impl MailBoxApi { /// The unique identifier of the account. account_id: Path, mailbox_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let account_id = account_id.0; let mailbox_id = mailbox_id.0; diff --git a/src/modules/rest/api/message.rs b/crates/server/src/rest/api/message.rs similarity index 87% rename from src/modules/rest/api/message.rs rename to crates/server/src/rest/api/message.rs index d0d43a5..dca5158 100644 --- a/src/modules/rest/api/message.rs +++ b/crates/server/src/rest/api/message.rs @@ -16,36 +16,36 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::AccountModel; -use crate::modules::common::auth::ClientContext; -use crate::modules::message::append::restore_emails; -use crate::modules::message::append::RestoreMessagesRequest; -use crate::modules::message::attachment::retrieve_attachment_content; -use crate::modules::message::attachment::retrieve_nested_attachment_content; -use crate::modules::message::content::retrieve_nested_eml_content; -use crate::modules::message::content::FullNestedMessageContent; -use crate::modules::message::content::{retrieve_email_content, FullMessageContent}; -use crate::modules::message::delete::delete_messages_impl; -use crate::modules::message::list::get_thread_messages; -use crate::modules::message::search::{search_messages_impl, EmailSearchRequest}; -use crate::modules::message::tags::TagCount; -use crate::modules::message::tags::TagsRequest; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::response::DataPage; -use crate::modules::rest::ApiResult; -use crate::modules::rest::ErrorCode; -use crate::modules::store::envelope::Envelope; -use crate::modules::store::storage::get_reader; -use crate::modules::store::tantivy::envelope::ENVELOPE_MANAGER; -use crate::modules::users::permissions::Permission; -use crate::raise_error; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::account::migration::AccountModel; +use bichon_core::common::paginated::DataPage; +use bichon_core::error::code::ErrorCode; +use bichon_core::message::append::restore_emails; +use bichon_core::message::append::RestoreMessagesRequest; +use bichon_core::message::attachment::retrieve_attachment_content; +use bichon_core::message::attachment::retrieve_nested_attachment_content; +use bichon_core::message::content::retrieve_nested_eml_content; +use bichon_core::message::content::FullNestedMessageContent; +use bichon_core::message::content::{retrieve_email_content, FullMessageContent}; +use bichon_core::message::delete::delete_messages_impl; +use bichon_core::message::list::get_thread_messages; +use bichon_core::message::search::{search_messages_impl, EmailSearchRequest}; +use bichon_core::message::tags::TagCount; +use bichon_core::message::tags::TagsRequest; +use bichon_core::raise_error; +use bichon_core::store::envelope::Envelope; +use bichon_core::store::storage::get_reader; +use bichon_core::store::tantivy::envelope::ENVELOPE_MANAGER; +use bichon_core::users::permissions::Permission; use poem::Body; use poem_openapi::param::{Path, Query}; use poem_openapi::payload::{Attachment, AttachmentType, Json}; use poem_openapi::OpenApi; -use tantivy::schema::Facet; use std::collections::HashMap; use std::collections::HashSet; +use tantivy::schema::Facet; pub struct MessageApi; @@ -61,7 +61,7 @@ impl MessageApi { &self, /// specifying the mailbox and messages to delete. payload: Json>>, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let request = payload.0; for account_id in request.keys() { @@ -82,7 +82,7 @@ impl MessageApi { async fn search_messages( &self, payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) @@ -111,7 +111,7 @@ impl MessageApi { page: Query, /// The number of messages per page. page_size: Query, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let account_id = account_id.0; let thread_id = thread_id.0.trim(); @@ -135,7 +135,7 @@ impl MessageApi { account_id: Path, /// The ID of the message to fetch. envelope_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; context @@ -159,7 +159,7 @@ impl MessageApi { /// The ID of the message to fetch. envelope_id: Path, content_hash: Query, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; context @@ -183,7 +183,7 @@ impl MessageApi { account_id: Path, /// The ID of the message. envelope_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; context @@ -217,7 +217,7 @@ impl MessageApi { account_id: Path, /// The ID of the message to download. envelope_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; AccountModel::check_account_exists(account_id).await?; @@ -244,7 +244,7 @@ impl MessageApi { account_id: Path, /// Message IDs to restore. payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let account_id = account_id.0; context @@ -267,7 +267,7 @@ impl MessageApi { envelope_id: Path, /// The content_hash of the attachment to download. content_hash: Query, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; let envelope_id = envelope_id.0.trim().to_string(); @@ -299,7 +299,7 @@ impl MessageApi { /// The filename of the attachment to download. content_hash: Query, nested_content_hash: Query, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account_id = account_id.0; let envelope_id = envelope_id.0.trim().to_string(); @@ -325,7 +325,7 @@ impl MessageApi { /// Returns all facets in the index along with their document counts. #[oai(path = "/all-tags", method = "get", operation_id = "get_all_tags")] - async fn get_all_tags(&self, context: ClientContext) -> ApiResult>> { + async fn get_all_tags(&self, context: WrappedContext) -> ApiResult>> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) .await @@ -346,7 +346,7 @@ impl MessageApi { async fn update_envelope_tags( &self, req: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let req = req.0; for tag in &req.tags { @@ -370,7 +370,7 @@ impl MessageApi { method = "get", operation_id = "get_all_contacts" )] - async fn get_all_contacts(&self, context: ClientContext) -> ApiResult>> { + async fn get_all_contacts(&self, context: WrappedContext) -> ApiResult>> { let authorized_ids: Option> = if context .has_permission(None, Permission::DATA_READ_ALL) .await diff --git a/src/modules/rest/api/mod.rs b/crates/server/src/rest/api/mod.rs similarity index 94% rename from src/modules/rest/api/mod.rs rename to crates/server/src/rest/api/mod.rs index 5d35ab1..2fc9a4e 100644 --- a/src/modules/rest/api/mod.rs +++ b/crates/server/src/rest/api/mod.rs @@ -19,17 +19,14 @@ use access_token::AccessTokenApi; use account::AccountApi; use auto_config::AutoConfigApi; +use bichon_core::bichon_version; use mailbox::MailBoxApi; use message::MessageApi; use oauth2::OAuth2Api; use poem_openapi::{OpenApiService, Tags}; +use crate::rest::api::{attachment::AttachmentApi, import::ImportApi, users::UsersApi}; use system::SystemApi; -use crate::{ - bichon_version, - modules::rest::api::{attachment::AttachmentApi, import::ImportApi, users::UsersApi}, -}; - pub mod access_token; pub mod account; pub mod attachment; diff --git a/src/modules/rest/api/oauth2.rs b/crates/server/src/rest/api/oauth2.rs similarity index 91% rename from src/modules/rest/api/oauth2.rs rename to crates/server/src/rest/api/oauth2.rs index 9c7056a..7e542c2 100644 --- a/src/modules/rest/api/oauth2.rs +++ b/crates/server/src/rest/api/oauth2.rs @@ -16,17 +16,18 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::account::migration::AccountModel; -use crate::modules::common::auth::ClientContext; -use crate::modules::error::code::ErrorCode; -use crate::modules::oauth2::entity::{OAuth2, OAuth2CreateRequest, OAuth2UpdateRequest}; -use crate::modules::oauth2::flow::{AuthorizeUrlRequest, OAuth2Flow}; -use crate::modules::oauth2::token::{ExternalOAuth2Request, OAuth2AccessToken}; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::response::DataPage; -use crate::modules::rest::ApiResult; -use crate::modules::users::permissions::Permission; -use crate::raise_error; +use bichon_core::account::migration::AccountModel; +use bichon_core::common::paginated::DataPage; +use bichon_core::error::code::ErrorCode; + +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::oauth2::entity::{OAuth2, OAuth2CreateRequest, OAuth2UpdateRequest}; +use bichon_core::oauth2::flow::{AuthorizeUrlRequest, OAuth2Flow}; +use bichon_core::oauth2::token::{ExternalOAuth2Request, OAuth2AccessToken}; +use bichon_core::raise_error; +use bichon_core::users::permissions::Permission; use poem_openapi::param::{Path, Query}; use poem_openapi::payload::{Json, PlainText}; use poem_openapi::OpenApi; @@ -48,7 +49,7 @@ impl OAuth2Api { &self, /// The id of the OAuth2 configuration to retrieve id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let id = id.0; let mut oauth2 = OAuth2::get(id).await?.ok_or_else(|| { @@ -77,7 +78,7 @@ impl OAuth2Api { &self, /// The name of the OAuth2 configuration to retrieve id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { context.require_permission(None, Permission::ROOT).await?; Ok(OAuth2::delete(id.0).await?) @@ -96,7 +97,7 @@ impl OAuth2Api { &self, /// A JSON payload containing the details for the new OAuth2 configuration request: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { context.require_permission(None, Permission::ROOT).await?; let entity = OAuth2::new(request.0)?; @@ -118,7 +119,7 @@ impl OAuth2Api { id: Path, /// A JSON payload containing the updated configuration details payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { context.require_permission(None, Permission::ROOT).await?; Ok(OAuth2::update(id.0, payload.0).await?) @@ -141,7 +142,7 @@ impl OAuth2Api { page_size: Query>, /// Optional. Whether to sort the list in descending order. desc: Query>, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let mut list = OAuth2::paginate_list(page.0, page_size.0, desc.0).await?; if context.has_permission(None, Permission::ROOT).await { @@ -168,7 +169,7 @@ impl OAuth2Api { &self, /// A JSON payload containing the OAuth2 configuration name and account ID. request: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let request = request.0; context @@ -194,7 +195,7 @@ impl OAuth2Api { &self, /// The ID of the account to retrieve access tokens for account_id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { let account = account_id.0; context @@ -235,7 +236,7 @@ impl OAuth2Api { &self, account_id: Path, request: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let account_id = account_id.0; AccountModel::check_account_exists(account_id).await?; diff --git a/src/modules/rest/api/system.rs b/crates/server/src/rest/api/system.rs similarity index 82% rename from src/modules/rest/api/system.rs rename to crates/server/src/rest/api/system.rs index 517a2d3..3e8f87b 100644 --- a/src/modules/rest/api/system.rs +++ b/crates/server/src/rest/api/system.rs @@ -16,17 +16,17 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::auth::ClientContext; -use crate::modules::dashboard::DashboardStats; -use crate::modules::error::code::ErrorCode; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::ApiResult; -use crate::modules::settings::cli::SETTINGS; -use crate::modules::settings::proxy::Proxy; -use crate::modules::settings::SystemConfigurations; -use crate::modules::users::permissions::Permission; -use crate::modules::version::{fetch_notifications, Notifications}; -use crate::raise_error; +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::dashboard::DashboardStats; +use bichon_core::error::code::ErrorCode; +use bichon_core::raise_error; +use bichon_core::settings::cli::SETTINGS; +use bichon_core::settings::proxy::Proxy; +use bichon_core::settings::SystemConfigurations; +use bichon_core::users::permissions::Permission; +use bichon_core::version::{fetch_notifications, Notifications}; use poem_openapi::param::Path; use poem_openapi::payload::{Json, PlainText}; use poem_openapi::OpenApi; @@ -35,7 +35,7 @@ pub struct SystemApi; #[OpenApi(prefix_path = "/api/v1", tag = "ApiTags::System")] impl SystemApi { - /// Retrieves important system notifications for the RustMail service. + /// Retrieves important system notifications for the Bichon service. /// /// This endpoint returns a consolidated view of all critical system notifications including: /// - Available version updates @@ -62,14 +62,17 @@ impl SystemApi { path = "/dashboard-stats", operation_id = "get_dashboard_stats" )] - async fn get_dashboard_stats(&self, context: ClientContext) -> ApiResult> { - let stats = DashboardStats::get(context).await?; + async fn get_dashboard_stats( + &self, + context: WrappedContext, + ) -> ApiResult> { + let stats = DashboardStats::get(context.0).await?; Ok(Json(stats)) } /// Get the full list of SOCKS5 proxy configurations. #[oai(method = "get", path = "/list-proxy", operation_id = "list_proxy")] - async fn list_proxy(&self, _context: ClientContext) -> ApiResult>> { + async fn list_proxy(&self, _context: WrappedContext) -> ApiResult>> { //The proxy list is visible to all users. let proxies = Proxy::list_all() .await @@ -83,7 +86,7 @@ impl SystemApi { &self, /// The ID of the proxy configuration to delete. id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { context.require_permission(None, Permission::ROOT).await?; Ok(Proxy::delete(id.0).await?) @@ -95,7 +98,7 @@ impl SystemApi { &self, /// The ID of the proxy configuration to retrieve. id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context.require_permission(None, Permission::ROOT).await?; Ok(Json(Proxy::get(id.0).await?)) @@ -103,7 +106,7 @@ impl SystemApi { /// Create a new proxy configuration. Requires root permission. #[oai(path = "/proxy", method = "post", operation_id = "create_proxy")] - async fn create_proxy(&self, url: PlainText, context: ClientContext) -> ApiResult<()> { + async fn create_proxy(&self, url: PlainText, context: WrappedContext) -> ApiResult<()> { context.require_permission(None, Permission::ROOT).await?; let entity = Proxy::new(url.0); Ok(entity.save().await?) @@ -115,7 +118,7 @@ impl SystemApi { &self, id: Path, url: PlainText, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { context.require_permission(None, Permission::ROOT).await?; Ok(Proxy::update(id.0, url.0).await?) @@ -131,7 +134,7 @@ impl SystemApi { )] async fn get_system_configurations( &self, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context.require_permission(None, Permission::ROOT).await?; let config: SystemConfigurations = SystemConfigurations::from(&*SETTINGS); diff --git a/src/modules/rest/api/users.rs b/crates/server/src/rest/api/users.rs similarity index 85% rename from src/modules/rest/api/users.rs rename to crates/server/src/rest/api/users.rs index c949b07..17a320f 100644 --- a/src/modules/rest/api/users.rs +++ b/crates/server/src/rest/api/users.rs @@ -18,18 +18,18 @@ use std::collections::BTreeMap; -use crate::modules::common::auth::ClientContext; -use crate::modules::rest::api::ApiTags; -use crate::modules::rest::ApiResult; -use crate::modules::token::AccessTokenModel; -use crate::modules::users::minimal::MinimalUser; -use crate::modules::users::payload::{ +use crate::common::auth::WrappedContext; +use crate::rest::api::ApiTags; +use crate::rest::ApiResult; +use bichon_core::token::AccessTokenModel; +use bichon_core::users::minimal::MinimalUser; +use bichon_core::users::payload::{ RoleCreateRequest, RoleUpdateRequest, UserCreateRequest, UserUpdateRequest, }; -use crate::modules::users::permissions::Permission; -use crate::modules::users::role::{RoleType, UserRole}; -use crate::modules::users::view::UserView; -use crate::modules::users::UserModel; +use bichon_core::users::permissions::Permission; +use bichon_core::users::role::{RoleType, UserRole}; +use bichon_core::users::view::UserView; +use bichon_core::users::UserModel; use poem::web::Path; use poem_openapi::payload::Json; use poem_openapi::OpenApi; @@ -39,7 +39,7 @@ pub struct UsersApi; #[OpenApi(prefix_path = "/api/v1", tag = "ApiTags::Users")] impl UsersApi { #[oai(path = "/list-roles", method = "get", operation_id = "list_roles")] - async fn list_roles(&self, context: ClientContext) -> ApiResult>> { + async fn list_roles(&self, context: WrappedContext) -> ApiResult>> { context .require_permission(None, Permission::USER_MANAGE) .await?; @@ -52,7 +52,7 @@ impl UsersApi { &self, /// The Role ID to delete id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let id = id.0; context @@ -67,7 +67,7 @@ impl UsersApi { &self, /// Role creation request payload payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context .require_permission(None, Permission::USER_MANAGE) @@ -84,7 +84,7 @@ impl UsersApi { id: Path, /// Role update request payload payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let id = id.0; context @@ -94,7 +94,7 @@ impl UsersApi { } #[oai(path = "/list-users", method = "get", operation_id = "list_users")] - async fn list_users(&self, context: ClientContext) -> ApiResult>> { + async fn list_users(&self, context: WrappedContext) -> ApiResult>> { context .require_permission(None, Permission::USER_MANAGE) .await?; @@ -113,7 +113,7 @@ impl UsersApi { async fn get_user_tokens( &self, id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let target_user_id = id.0; let tokens = AccessTokenModel::get_user_api_tokens(target_user_id).await?; @@ -131,7 +131,7 @@ impl UsersApi { &self, /// The User ID to delete id: Path, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let id = id.0; context @@ -144,7 +144,7 @@ impl UsersApi { async fn create_user( &self, payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult> { context .require_permission(None, Permission::USER_MANAGE) @@ -160,7 +160,7 @@ impl UsersApi { &self, id: Path, payload: Json, - context: ClientContext, + context: WrappedContext, ) -> ApiResult<()> { let target_id = id.0; let current_user_id = context.user.id; @@ -185,10 +185,10 @@ impl UsersApi { method = "get", operation_id = "get_current_user" )] - async fn get_current_user(&self, context: ClientContext) -> ApiResult> { + async fn get_current_user(&self, context: WrappedContext) -> ApiResult> { let roles = UserRole::list_all().await?; let role_lookup: BTreeMap = roles.into_iter().map(|r| (r.id, r)).collect(); - Ok(Json(context.user.to_view(&role_lookup))) + Ok(Json(context.0.user.to_view(&role_lookup))) } #[oai( @@ -198,7 +198,7 @@ impl UsersApi { )] async fn get_minimal_user_list( &self, - context: ClientContext, + context: WrappedContext, ) -> ApiResult>> { let is_admin = context.user.is_admin().await; let minimal_list = MinimalUser::list_all().await?; @@ -217,7 +217,7 @@ impl UsersApi { method = "get", operation_id = "list_account_roles" )] - async fn list_account_roles(&self, context: ClientContext) -> ApiResult>> { + async fn list_account_roles(&self, context: WrappedContext) -> ApiResult>> { context .require_permission(None, Permission::USER_VIEW) .await?; diff --git a/src/modules/rest/assets.rs b/crates/server/src/rest/assets.rs similarity index 96% rename from src/modules/rest/assets.rs rename to crates/server/src/rest/assets.rs index 837be65..7675629 100644 --- a/src/modules/rest/assets.rs +++ b/crates/server/src/rest/assets.rs @@ -20,5 +20,5 @@ use rust_embed::RustEmbed; #[derive(RustEmbed)] -#[folder = "web/dist/"] +#[folder = "../../web/dist/"] pub struct FrontEndAssets; diff --git a/src/modules/rest/mod.rs b/crates/server/src/rest/mod.rs similarity index 90% rename from src/modules/rest/mod.rs rename to crates/server/src/rest/mod.rs index c45aeef..f9b0b32 100644 --- a/src/modules/rest/mod.rs +++ b/crates/server/src/rest/mod.rs @@ -16,23 +16,23 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::common::error::ErrorCapture; -use crate::modules::common::log::Tracing; -use crate::modules::common::signal::SIGNAL_MANAGER; -use crate::modules::common::tls::rustls_config; -use crate::modules::error::code::ErrorCode; -use crate::modules::error::handler::error_handler; -use crate::modules::error::BichonResult; -use crate::modules::rest::public::login::login; -use crate::modules::rest::public::status::get_status; -use crate::modules::settings::cli::SETTINGS; +use crate::common::error::ErrorCapture; +use crate::common::log::Tracing; +use crate::common::tls::rustls_config; +use crate::error::handler::error_handler; +use crate::rest::public::login::login; +use crate::rest::public::status::get_status; +use bichon_core::common::signal::SIGNAL_MANAGER; +use bichon_core::error::code::ErrorCode; +use bichon_core::error::BichonResult; +use bichon_core::settings::cli::SETTINGS; use super::error::ApiErrorResponse; -use crate::modules::common::auth::ApiGuard; -use crate::modules::common::timeout::{Timeout, TIMEOUT_HEADER}; -use crate::raise_error; +use crate::common::auth::ApiGuard; +use crate::common::timeout::{Timeout, TIMEOUT_HEADER}; use api::create_openapi_service; use assets::FrontEndAssets; +use bichon_core::raise_error; use http::{HeaderValue, Method}; use poem::endpoint::EmbeddedFilesEndpoint; use poem::listener::{Listener, TcpListener}; @@ -46,7 +46,6 @@ use std::time::Duration; pub mod api; pub mod assets; pub mod public; -pub mod response; pub type ApiResult = std::result::Result; diff --git a/src/modules/rest/public/login.rs b/crates/server/src/rest/public/login.rs similarity index 98% rename from src/modules/rest/public/login.rs rename to crates/server/src/rest/public/login.rs index 60dfcff..1369618 100644 --- a/src/modules/rest/public/login.rs +++ b/crates/server/src/rest/public/login.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::modules::users::UserModel; +use bichon_core::users::UserModel; use poem::{handler, web::Json, IntoResponse, Response}; use serde::Deserialize; use tracing::error; diff --git a/src/modules/rest/public/mod.rs b/crates/server/src/rest/public/mod.rs similarity index 100% rename from src/modules/rest/public/mod.rs rename to crates/server/src/rest/public/mod.rs diff --git a/src/modules/rest/public/oauth2.rs b/crates/server/src/rest/public/oauth2.rs similarity index 97% rename from src/modules/rest/public/oauth2.rs rename to crates/server/src/rest/public/oauth2.rs index 0f67f6b..bae454c 100644 --- a/src/modules/rest/public/oauth2.rs +++ b/crates/server/src/rest/public/oauth2.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . -use crate::modules::oauth2::{flow::OAuth2Flow, pending::OAuth2PendingEntity}; +use bichon_core::oauth2::{flow::OAuth2Flow, pending::OAuth2PendingEntity}; use poem::{ handler, web::{Query, Redirect}, diff --git a/src/modules/rest/public/status.rs b/crates/server/src/rest/public/status.rs similarity index 94% rename from src/modules/rest/public/status.rs rename to crates/server/src/rest/public/status.rs index 8f24456..b08cbbc 100644 --- a/src/modules/rest/public/status.rs +++ b/crates/server/src/rest/public/status.rs @@ -17,9 +17,10 @@ // along with this program. If not, see . -use crate::modules::context::status::BichonStatus; use poem::{handler, web::Json, IntoResponse}; +use crate::common::status::BichonStatus; + #[handler] pub async fn get_status() -> impl IntoResponse { Json(BichonStatus::get()) diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml new file mode 100644 index 0000000..51f17c8 --- /dev/null +++ b/crates/smtp/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bichon-smtp" +version.workspace = true +edition.workspace = true + + +[dependencies] +bichon-core = { path = "../core" } +rcgen.workspace = true +rustls-pemfile.workspace = true +tokio-util.workspace = true +tokio.workspace = true +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true +base64.workspace = true +tokio-rustls.workspace = true +rustls.workspace = true +rustls-pki-types.workspace = true + +[dev-dependencies] +#bincode = "1.3.3" +#secret-lib = "1.0.0" +tempfile = "3.27.0" +lettre = "0.11.21" \ No newline at end of file diff --git a/crates/smtp/src/lib.rs b/crates/smtp/src/lib.rs new file mode 100644 index 0000000..a4b3210 --- /dev/null +++ b/crates/smtp/src/lib.rs @@ -0,0 +1,6 @@ +pub mod server; + +mod stream; +#[cfg(test)] +mod tests; +mod tls; diff --git a/src/modules/smtp/server.rs b/crates/smtp/src/server.rs similarity index 89% rename from src/modules/smtp/server.rs rename to crates/smtp/src/server.rs index 418b60d..e6ca424 100644 --- a/src/modules/smtp/server.rs +++ b/crates/smtp/src/server.rs @@ -17,21 +17,23 @@ // along with this program. If not, see . use std::io; +use std::net::SocketAddr; use std::time::Duration; -use crate::modules::cache::imap::mailbox::{Attribute, AttributeEnum}; -use crate::modules::envelope::extractor::extract_envelope_from_smtp; -use crate::modules::error::BichonResult; -use crate::modules::utils::create_hash; -use crate::modules::{ +use base64::{prelude::BASE64_STANDARD, Engine as _}; +use bichon_core::cache::imap::mailbox::{Attribute, AttributeEnum}; +use bichon_core::common::signal::SIGNAL_MANAGER; +use bichon_core::envelope::extractor::extract_envelope_from_smtp; +use bichon_core::error::BichonResult; +use bichon_core::settings::cli::{SETTINGS, SmtpEncryptionMode}; +use bichon_core::utils::create_hash; +use bichon_core::{ account::migration::AccountModel, cache::imap::mailbox::MailBox, common::auth::ClientContext, - smtp::{stream::BufStream, SmtpConfig}, token::AccessTokenModel, users::{permissions::Permission, UserModel}, }; -use base64::{prelude::BASE64_STANDARD, Engine as _}; use tokio::time::timeout; use tokio::{ io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt}, @@ -40,6 +42,9 @@ use tokio::{ }; use tokio_rustls::TlsAcceptor; +use crate::stream::BufStream; +use crate::tls::create_acceptor; + const MAX_MAIL_SIZE: usize = 50 * 1024 * 1024; //50MB const SMTP_IDLE_TIMEOUT: Duration = Duration::from_secs(60); const GLOBAL_SESSION_TIMEOUT: Duration = Duration::from_secs(600); @@ -643,3 +648,75 @@ async fn parse_email(data: &[u8], session: &Session) -> BichonResult<()> { e }) } + +#[derive(Clone, Default)] +pub struct SmtpConfig { + pub whitelist: Option>, + pub tls_acceptor: Option, + pub auth_required: bool, +} + +pub struct SmtpServer { + pub smtp_addr: SocketAddr, + smtp_handle: tokio::task::JoinHandle<()>, +} + +impl SmtpServer { + pub async fn stop(self) { + let _ = self.smtp_handle.await; + } +} + +pub async fn start_smtp_server() -> std::io::Result { + let smtp_port = SETTINGS.bichon_smtp_port; + + let tls_acceptor: Option = match SETTINGS.bichon_smtp_encryption { + SmtpEncryptionMode::None => None, + SmtpEncryptionMode::Starttls | SmtpEncryptionMode::Tls => Some(create_acceptor().await?), + }; + + let smtp_listener = TcpListener::bind(( + SETTINGS.bichon_bind_ip.clone().unwrap_or("0.0.0.0".into()), + smtp_port, + )) + .await + .map_err(|e| { + if e.kind() == std::io::ErrorKind::AddrInUse { + std::io::Error::other(format!( + "SMTP port {smtp_port} is already in use. Is another instance running?" + )) + } else { + e + } + })?; + let smtp_addr = smtp_listener.local_addr()?; + + let smtp_config = SmtpConfig { + whitelist: None, + tls_acceptor: match SETTINGS.bichon_smtp_encryption { + SmtpEncryptionMode::None | SmtpEncryptionMode::Tls => None, + SmtpEncryptionMode::Starttls => tls_acceptor.clone(), + }, + auth_required: SETTINGS.bichon_smtp_auth_required, + }; + + let smtp_shutdown = SIGNAL_MANAGER.subscribe(); + + let smtp_handle = if matches!(SETTINGS.bichon_smtp_encryption, SmtpEncryptionMode::Tls) { + let acceptor = tls_acceptor + .clone() + .expect("TLS acceptor required when tls=true"); + tokio::spawn(async move { + run_smtps_server(smtp_listener, smtp_config, acceptor, smtp_shutdown).await; + }) + } else { + tokio::spawn(async move { + run_smtp_server(smtp_listener, smtp_config, smtp_shutdown).await; + }) + }; + + Ok(SmtpServer { + smtp_addr, + smtp_handle, + }) +} diff --git a/src/modules/smtp/stream.rs b/crates/smtp/src/stream.rs similarity index 100% rename from src/modules/smtp/stream.rs rename to crates/smtp/src/stream.rs diff --git a/src/modules/smtp/tests.rs b/crates/smtp/src/tests.rs similarity index 100% rename from src/modules/smtp/tests.rs rename to crates/smtp/src/tests.rs diff --git a/src/modules/smtp/tls.rs b/crates/smtp/src/tls.rs similarity index 98% rename from src/modules/smtp/tls.rs rename to crates/smtp/src/tls.rs index ac37715..76f0bd4 100644 --- a/src/modules/smtp/tls.rs +++ b/crates/smtp/src/tls.rs @@ -25,7 +25,7 @@ use tokio::fs::File; use tokio::io::AsyncReadExt; use tokio_rustls::TlsAcceptor; -use crate::modules::settings::cli::SETTINGS; +use bichon_core::settings::cli::SETTINGS; pub async fn create_acceptor() -> io::Result { let (certs, key) = if let (Some(key_path), Some(cert_path)) = ( diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 0f87eac..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pub mod modules; \ No newline at end of file diff --git a/src/modules/cli/mod.rs b/src/modules/cli/mod.rs deleted file mode 100644 index f1626fc..0000000 --- a/src/modules/cli/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - - -use clap::Parser; -use serde::{Deserialize, Serialize}; - -use crate::bichon_version; - -pub mod admin; -pub mod auth; -pub mod eml; -pub mod mbox; -pub mod pst; -pub mod sender; -pub mod thunderbird; - -#[derive(Parser, Debug)] -#[command( - name = "bichonctl", - author = "rustmailer", - version = bichon_version!(), - about = "A CLI tool to import email data into Bichon service" -)] -pub struct BichonCli { - /// Path to the configuration file - #[arg( - short, - long, - default_value = "config.toml", - value_name = "FILE", - help = "Sets a custom config file" - )] - pub config: std::path::PathBuf, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct BichonCtlConfig { - pub base_url: String, - pub api_token: String, -} diff --git a/src/modules/common/auth.rs b/src/modules/common/auth.rs deleted file mode 100644 index 75140f5..0000000 --- a/src/modules/common/auth.rs +++ /dev/null @@ -1,283 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use crate::{ - modules::{ - error::{code::ErrorCode, BichonResult}, - token::AccessTokenModel, - users::{permissions::Permission, role::UserRole, UserModel}, - utils::rate_limit::RATE_LIMITER_MANAGER, - }, - raise_error, -}; -use governor::clock::{Clock, QuantaClock}; -use poem::{ - web::{ - headers::{authorization::Bearer, Authorization, HeaderMapExt}, - RealIp, - }, - Endpoint, FromRequest, Middleware, Request, RequestBody, Result, -}; -use serde::Deserialize; -use std::{ - collections::{BTreeSet, HashSet}, - net::IpAddr, - sync::Arc, -}; - -use super::create_api_error_response; - -pub struct ApiGuard; - -pub struct ApiGuardEndpoint { - ep: E, -} - -impl Middleware for ApiGuard { - type Output = ApiGuardEndpoint; - - fn transform(&self, ep: E) -> Self::Output { - ApiGuardEndpoint { ep } - } -} - -#[derive(Deserialize)] -struct Param { - access_token: String, -} - -impl Endpoint for ApiGuardEndpoint { - type Output = E::Output; - - async fn call(&self, mut req: Request) -> Result { - let context = authorize_access(&req).await?; - req.set_data(Arc::new(context)); - self.ep.call(req).await - } -} - -#[derive(Clone, Debug)] -pub struct ClientContext { - pub ip_addr: Option, - pub user: UserModel, -} - -impl ClientContext { - pub async fn require_any_permission( - &self, - requirements: Vec<(Option, &str)>, - ) -> BichonResult<()> { - for (account_id, permission) in requirements { - if self.has_permission(account_id, permission).await { - return Ok(()); - } - } - Err(raise_error!( - "Access denied: Insufficient permissions to perform this action.".into(), - ErrorCode::Forbidden - )) - } - - pub async fn check_has_permission(user: &UserModel, account_id: Option, permission: &str) -> bool { - if user.is_admin().await { - return true; - } - - let mut global_perms = HashSet::new(); - for rid in &user.global_roles { - if let Some(role) = UserRole::find(*rid).await.ok().flatten() { - global_perms.extend(role.permissions); - } - } - - if Self::check_global_logic(&global_perms, permission) { - return true; - } - - if let Some(aid) = account_id { - if let Some(role_id) = user.account_access_map.get(&aid) { - if let Some(role) = UserRole::find(*role_id).await.ok().flatten() { - if role.permissions.contains(&permission.to_string()) - || Self::check_account_logic(&role.permissions, permission) - { - return true; - } - } - } - } - - false - } - - pub async fn has_permission(&self, account_id: Option, permission: &str) -> bool { - if self.user.is_admin().await { - return true; - } - - let mut global_perms = HashSet::new(); - for rid in &self.user.global_roles { - if let Some(role) = UserRole::find(*rid).await.ok().flatten() { - global_perms.extend(role.permissions); - } - } - - if Self::check_global_logic(&global_perms, permission) { - return true; - } - - if let Some(aid) = account_id { - if let Some(role_id) = self.user.account_access_map.get(&aid) { - if let Some(role) = UserRole::find(*role_id).await.ok().flatten() { - if role.permissions.contains(&permission.to_string()) - || Self::check_account_logic(&role.permissions, permission) - { - return true; - } - } - } - } - - false - } - - fn check_global_logic(global: &HashSet, perm: &str) -> bool { - if global.contains(perm) { - return true; - } - - match perm { - Permission::DATA_READ => global.contains(Permission::DATA_READ_ALL), - Permission::DATA_DELETE => global.contains(Permission::DATA_DELETE_ALL), - Permission::DATA_RAW_DOWNLOAD => global.contains(Permission::DATA_RAW_DOWNLOAD_ALL), - Permission::DATA_EXPORT_BATCH => global.contains(Permission::DATA_EXPORT_BATCH_ALL), - Permission::ACCOUNT_MANAGE | Permission::ACCOUNT_READ_DETAILS => { - global.contains(Permission::ACCOUNT_MANAGE_ALL) - } - _ => false, - } - } - - fn check_account_logic(scoped_perms: &BTreeSet, perm: &str) -> bool { - if scoped_perms.contains(perm) { - return true; - } - match perm { - Permission::DATA_READ | Permission::ACCOUNT_READ_DETAILS => { - scoped_perms.contains(Permission::ACCOUNT_MANAGE) - } - _ => false, - } - } - - pub async fn require_permission( - &self, - account_id: Option, - permission: &str, - ) -> BichonResult<()> { - if self.has_permission(account_id, permission).await { - Ok(()) - } else { - Err(raise_error!( - format!("Access Denied: Missing permission '{}'", permission), - ErrorCode::Forbidden - )) - } - } -} - -impl<'a> FromRequest<'a> for ClientContext { - async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result { - extract_client_context(req).await - } -} - -pub async fn extract_client_context(req: &Request) -> Result { - let ip_addr = RealIp::from_request_without_body(req) - .await - .map_err(|_| { - create_api_error_response( - "Failed to parse client IP address", - ErrorCode::InvalidParameter, - ) - })? - .0 - .ok_or_else(|| { - create_api_error_response( - "Failed to parse client IP address", - ErrorCode::InvalidParameter, - ) - })?; - // Extract access token from Bearer header or query params - let bearer = req - .headers() - .typed_get::>() - .map(|auth| auth.0.token().to_string()) - .or_else(|| req.params::().ok().map(|param| param.access_token)); - - let token = bearer.ok_or_else(|| { - create_api_error_response("Valid access token not found", ErrorCode::PermissionDenied) - })?; - - // Validate and update access token - let user = AccessTokenModel::resolve_user_from_token(&token) - .await - .map_err(|e| { - create_api_error_response(&format!("{:#?}", e), ErrorCode::PermissionDenied) - })?; - - return Ok(ClientContext { - ip_addr: Some(ip_addr), - user, - }); -} - -pub async fn authorize_access(req: &Request) -> Result { - let context = extract_client_context(&req).await?; - if let Some(access_control) = &context.user.acl { - if let Some(ip_addr) = context.ip_addr { - if let Some(whitelist) = &access_control.ip_whitelist { - if !whitelist.contains(&ip_addr.to_string()) { - return Err(create_api_error_response( - &format!("IP {} not in whitelist", ip_addr), - ErrorCode::Forbidden, - )); - } - } - } - - if let Some(rate_limit) = &access_control.rate_limit { - if let Err(not_until) = RATE_LIMITER_MANAGER - .check(context.user.id, rate_limit.clone()) - .await - { - let wait_duration = not_until.wait_time_from(QuantaClock::default().now()); - return Err(create_api_error_response( - &format!( - "Rate limit: {}/{}s. Retry after {}s", - rate_limit.quota, - rate_limit.interval, - wait_duration.as_secs() - ), - ErrorCode::TooManyRequest, - )); - } - } - } - - Ok(context) -} diff --git a/src/modules/common/paginated.rs b/src/modules/common/paginated.rs deleted file mode 100644 index a6b7bd5..0000000 --- a/src/modules/common/paginated.rs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - - -use std::cmp::min; - -use crate::{ - modules::{ - database::Paginated, - error::{code::ErrorCode, BichonResult}, - }, - raise_error, -}; - -pub fn paginate_vec( - items: &Vec, - page: Option, - page_size: Option, -) -> BichonResult> { - let total_items = items.len() as u64; - - let (offset, total_pages) = match (page, page_size) { - (Some(p), Some(s)) if p > 0 && s > 0 => { - let offset = (p - 1) * s; - let total_pages = if total_items > 0 { - (total_items + s - 1) / s - } else { - 0 - }; - (Some(offset), Some(total_pages)) - } - (Some(0), _) | (_, Some(0)) => { - return Err(raise_error!( - "'page' and 'page_size' must be greater than 0.".into(), - ErrorCode::InvalidParameter - )); - } - _ => (None, None), - }; - - let data = match offset { - Some(offset) if offset >= total_items => vec![], - Some(offset) => { - let end = min(offset + page_size.unwrap_or(total_items), total_items) as usize; - items[offset as usize..end].to_vec() - } - None => items.clone(), - }; - - Ok(Paginated::new( - page, - page_size, - total_items, - total_pages, - data, - )) -} diff --git a/src/modules/mod.rs b/src/modules/mod.rs deleted file mode 100644 index 938bbd0..0000000 --- a/src/modules/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pub mod account; -pub mod autoconfig; -pub mod cache; -pub mod cli; -pub mod common; -pub mod context; -pub mod dashboard; -pub mod database; -pub mod envelope; -pub mod error; -pub mod imap; -pub mod import; -pub mod logger; -pub mod mailbox; -pub mod message; -pub mod oauth2; -pub mod rest; -pub mod settings; -pub mod smtp; -pub mod store; -pub mod tasks; -pub mod token; -pub mod users; -pub mod utils; -pub mod version; diff --git a/src/modules/rest/response.rs b/src/modules/rest/response.rs deleted file mode 100644 index eea4561..0000000 --- a/src/modules/rest/response.rs +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - - -use poem_openapi::Object; -use serde::{Deserialize, Serialize}; - -use crate::modules::database::Paginated; - -/// Represents a paginated response containing a subset of items along with pagination metadata. -/// -/// This generic structure is commonly used to return paged data from list or search endpoints. -/// The type `S` represents the individual item type within the result set. -/// -/// # Type Parameters -/// - `S`: The type of each item in the `items` list. Must implement several traits for serialization -/// and OpenAPI documentation. -/// -/// # Fields -/// - `current_page`: The current page number (1-based). `None` if unspecified or not applicable. -/// - `page_size`: The number of items per page. `None` if unspecified. -/// - `total_items`: The total number of items matching the query. -/// - `items`: The list of items returned for the current page. -/// - `total_pages`: The total number of pages available. `None` if not calculated. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Object)] -pub struct DataPage -where - S: Serialize - + std::fmt::Debug - + std::marker::Unpin - + Send - + Sync - + poem_openapi::types::Type - + poem_openapi::types::ParseFromJSON - + poem_openapi::types::ToJSON, -{ - /// The current page number (starting from 1). - pub current_page: Option, - /// The number of items per page. - pub page_size: Option, - /// The total number of items across all pages. - pub total_items: u64, - /// The list of items returned on the current page. - pub items: Vec, - /// The total number of pages. This is optional and may not be set if not calculated. - pub total_pages: Option, -} - -impl< - S: Serialize - + std::fmt::Debug - + std::marker::Unpin - + Send - + Sync - + poem_openapi::types::Type - + poem_openapi::types::ParseFromJSON - + poem_openapi::types::ToJSON, - > From> for DataPage -{ - fn from(paginated: Paginated) -> Self { - DataPage { - current_page: paginated.page, - page_size: paginated.page_size, - total_items: paginated.total_items, - total_pages: paginated.total_pages, - items: paginated.items, - } - } -} diff --git a/src/modules/smtp/mod.rs b/src/modules/smtp/mod.rs deleted file mode 100644 index 6746f06..0000000 --- a/src/modules/smtp/mod.rs +++ /dev/null @@ -1,109 +0,0 @@ -// -// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use std::net::SocketAddr; - -use tokio::net::TcpListener; -use tokio_rustls::TlsAcceptor; - -use crate::modules::{ - common::signal::SIGNAL_MANAGER, - settings::cli::{SmtpEncryptionMode, SETTINGS}, - smtp::{ - server::{run_smtp_server, run_smtps_server}, - tls::create_acceptor, - }, -}; - -pub mod server; -pub mod stream; -pub mod tls; -#[cfg(test)] -mod tests; - -#[derive(Clone, Default)] -pub struct SmtpConfig { - pub whitelist: Option>, - pub tls_acceptor: Option, - pub auth_required: bool, -} - -pub struct SmtpServer { - pub smtp_addr: SocketAddr, - smtp_handle: tokio::task::JoinHandle<()>, -} - -impl SmtpServer { - pub async fn stop(self) { - let _ = self.smtp_handle.await; - } -} - -pub async fn start_smtp_server() -> std::io::Result { - let smtp_port = SETTINGS.bichon_smtp_port; - - let tls_acceptor: Option = match SETTINGS.bichon_smtp_encryption { - SmtpEncryptionMode::None => None, - SmtpEncryptionMode::Starttls | SmtpEncryptionMode::Tls => Some(create_acceptor().await?), - }; - - let smtp_listener = TcpListener::bind(( - SETTINGS.bichon_bind_ip.clone().unwrap_or("0.0.0.0".into()), - smtp_port, - )) - .await - .map_err(|e| { - if e.kind() == std::io::ErrorKind::AddrInUse { - std::io::Error::other(format!( - "SMTP port {smtp_port} is already in use. Is another instance running?" - )) - } else { - e - } - })?; - let smtp_addr = smtp_listener.local_addr()?; - - let smtp_config = SmtpConfig { - whitelist: None, - tls_acceptor: match SETTINGS.bichon_smtp_encryption { - SmtpEncryptionMode::None | SmtpEncryptionMode::Tls => None, - SmtpEncryptionMode::Starttls => tls_acceptor.clone(), - }, - auth_required: SETTINGS.bichon_smtp_auth_required, - }; - - let smtp_shutdown = SIGNAL_MANAGER.subscribe(); - - let smtp_handle = if matches!(SETTINGS.bichon_smtp_encryption, SmtpEncryptionMode::Tls) { - let acceptor = tls_acceptor - .clone() - .expect("TLS acceptor required when tls=true"); - tokio::spawn(async move { - run_smtps_server(smtp_listener, smtp_config, acceptor, smtp_shutdown).await; - }) - } else { - tokio::spawn(async move { - run_smtp_server(smtp_listener, smtp_config, smtp_shutdown).await; - }) - }; - - Ok(SmtpServer { - smtp_addr, - smtp_handle, - }) -} diff --git a/web/src/api/system/api.ts b/web/src/api/system/api.ts index cfac15e..6cae7c6 100644 --- a/web/src/api/system/api.ts +++ b/web/src/api/system/api.ts @@ -55,7 +55,6 @@ export interface DashboardStats { top_largest_emails: LargestEmail[]; // Top 10 largest emails top_largest_attachments: LargestAttachment[]; // Top 10 largest attachments system_version: string, //The semantic version string of the currently running backend service - commit_hash: string //Git commit hash used to build this system version } export const INITIAL_DASHBOARD_STATS: DashboardStats = { @@ -71,8 +70,7 @@ export const INITIAL_DASHBOARD_STATS: DashboardStats = { without_attachment_count: 0, top_largest_emails: [], top_largest_attachments: [], - system_version: '0.0.0', - commit_hash: 'n/a' + system_version: '0.0.0' }; diff --git a/web/src/features/dashboard/index.tsx b/web/src/features/dashboard/index.tsx index 4f71fe8..45067d4 100644 --- a/web/src/features/dashboard/index.tsx +++ b/web/src/features/dashboard/index.tsx @@ -233,7 +233,6 @@ export default function MailArchiveDashboard() {
-

{stats1.commit_hash?.substring(0, 7) ?? 'N/A'}

diff --git a/web/src/features/settings/profile/profile-form.tsx b/web/src/features/settings/profile/profile-form.tsx index 52bbd4a..bd18c0f 100644 --- a/web/src/features/settings/profile/profile-form.tsx +++ b/web/src/features/settings/profile/profile-form.tsx @@ -267,7 +267,7 @@ export function UserProfileForm({ user }: UserProfileFormProps) { {hasAccess && (
-

+

{t('settings.profile.section.accounts', { count: accessibleAccountIds.length, })}