/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * SPDX-FileCopyrightText: 2026 John Coffey * * SPDX-License-Identifier: Apache-2.0 OR MIT */ pub mod admin; pub mod data; pub mod error; pub mod jmap; pub mod seed; use admin::Admin; use error::SeedResult; use jmap::Jmap; use seed::{FileSpec, Layout, MailboxSpec}; pub const ADMIN_USER: &str = "admin"; pub const ADMIN_PASSWORD: &str = "admin"; pub const DOMAIN: &str = "inbuxa-migrate.org"; pub const USER_PASSWORD: &str = "InbuxaUser#2026"; pub const ADMIN_ACCOUNT_PASSWORD: &str = "InbuxaAdmin#2026"; pub const SYNC_IN: [&str; 3] = ["test1", "test2", "test3"]; pub const SYNC_OUT: [&str; 3] = ["test4", "test5", "test6"]; pub const ADMIN_LOCALPART: &str = "vandeladmin"; pub const CUSTOM_IDENTITY_NAME: &str = "Inbuxa Test (Custom)"; macro_rules! mailbox { ($k:expr, $n:expr, $p:expr, $r:expr) => { MailboxSpec { key: $k, name: $n, parent: $p, role: $r, } }; } macro_rules! node { ($k:expr, $n:expr, $p:expr, $d:expr) => { FileSpec { key: $k, name: $n, parent: $p, directory: $d, } }; } static LAYOUT1_MAILBOXES: &[MailboxSpec] = &[ mailbox!("proj", "Projects", None, None), mailbox!("alpha", "Alpha", Some("proj"), None), mailbox!("sub", "Subtasks", Some("alpha"), None), mailbox!("done", "Done", Some("sub"), None), mailbox!("beta", "Beta", Some("proj"), None), mailbox!("backlog", "Backlog", Some("beta"), None), mailbox!("arch", "Archive", None, Some("archive")), ]; static LAYOUT2_MAILBOXES: &[MailboxSpec] = &[ mailbox!("work", "Work", None, None), mailbox!("clients", "Clients", Some("work"), None), mailbox!("acme", "Acme Corp", Some("clients"), None), mailbox!("inv", "Invoices", Some("acme"), None), mailbox!("globex", "Globex", Some("clients"), None), mailbox!("internal", "Internal", Some("work"), None), mailbox!("pers", "Personal", None, None), mailbox!("rcpt", "Receipts", Some("pers"), None), mailbox!("y2025", "2025", Some("rcpt"), None), ]; static LAYOUT3_MAILBOXES: &[MailboxSpec] = &[ mailbox!("lists", "Lists", None, None), mailbox!("news", "Newsletters", Some("lists"), None), mailbox!("tech", "Tech", Some("news"), None), mailbox!("notif", "Notifications", Some("lists"), None), mailbox!("misc", "Misc", None, None), ]; static LAYOUT1_FILES: &[FileSpec] = &[ node!("d_docs", "Documents", None, true), node!("d_rep", "Reports", Some("d_docs"), true), node!("d_24", "2024", Some("d_rep"), true), node!("f_q4", "q4-results.bin", Some("d_24"), false), node!("d_25", "2025", Some("d_rep"), true), node!("f_q1", "q1-results.bin", Some("d_25"), false), node!("d_spec", "Specs", Some("d_docs"), true), node!("f_ovr", "overview.bin", Some("d_spec"), false), node!("f_readme", "README.bin", None, false), ]; static LAYOUT2_FILES: &[FileSpec] = &[ node!("m_media", "Media", None, true), node!("m_photos", "Photos", Some("m_media"), true), node!("m_23", "2023", Some("m_photos"), true), node!("f_trip", "trip.bin", Some("m_23"), false), node!("m_audio", "Audio", Some("m_media"), true), node!("f_notes", "notes.bin", Some("m_audio"), false), node!("m_proj", "Projects", None, true), node!("m_src", "src", Some("m_proj"), true), node!("f_main", "main.bin", Some("m_src"), false), ]; static LAYOUT3_FILES: &[FileSpec] = &[ node!("b_bak", "Backup", None, true), node!("b_db", "db", Some("b_bak"), true), node!("f_snap", "snapshot.bin", Some("b_db"), false), node!("f_scratch", "scratch.bin", None, false), ]; fn layout_for(localpart: &str) -> Layout { match localpart { "test1" => Layout { mailboxes: LAYOUT1_MAILBOXES, file_tree: LAYOUT1_FILES, email_count: 400, sieve_active: Some(true), identity: true, extra_address_book: true, extra_calendar: true, }, "test2" => Layout { mailboxes: LAYOUT2_MAILBOXES, file_tree: LAYOUT2_FILES, email_count: 110, sieve_active: Some(false), identity: true, extra_address_book: false, extra_calendar: true, }, _ => Layout { mailboxes: LAYOUT3_MAILBOXES, file_tree: LAYOUT3_FILES, email_count: 70, sieve_active: None, identity: false, extra_address_book: false, extra_calendar: false, }, } } pub struct AccountFixture { pub localpart: String, pub email: String, pub password: String, pub admin_role: bool, pub account_id: String, pub seeded: Option, } pub struct Fixture { pub base_url: String, pub domain: String, pub domain_id: String, pub admin_login: (String, String), pub accounts: Vec, } impl Fixture { pub fn account(&self, localpart: &str) -> Option<&AccountFixture> { self.accounts.iter().find(|a| a.localpart == localpart) } } fn email_of(localpart: &str) -> String { format!("{localpart}@{DOMAIN}") } pub fn teardown(base_url: &str) -> SeedResult<()> { let admin = Admin::connect(base_url, ADMIN_USER, ADMIN_PASSWORD)?; admin.teardown_domain(DOMAIN)?; admin.invalidate_caches() } pub fn provision(base_url: &str) -> SeedResult { let admin = Admin::connect(base_url, ADMIN_USER, ADMIN_PASSWORD)?; admin.teardown_domain(DOMAIN)?; admin.invalidate_caches()?; let domain_id = admin.ensure_domain(DOMAIN)?; admin.invalidate_caches()?; let mut specs: Vec<(String, String, bool)> = Vec::new(); for lp in SYNC_IN.iter().chain(SYNC_OUT.iter()) { specs.push(((*lp).to_owned(), USER_PASSWORD.to_owned(), false)); } specs.push(( ADMIN_LOCALPART.to_owned(), ADMIN_ACCOUNT_PASSWORD.to_owned(), true, )); let mut accounts = Vec::new(); for (localpart, password, admin_role) in &specs { admin.create_account(localpart, &domain_id, password, *admin_role)?; } admin.invalidate_caches()?; for (localpart, password, admin_role) in &specs { let email = email_of(localpart); let user = Jmap::connect(base_url, &email, password)?; accounts.push(AccountFixture { localpart: localpart.clone(), email, password: password.clone(), admin_role: *admin_role, account_id: user.account_id.clone(), seeded: None, }); } let messages = data::load_mbox(usize::MAX)?; let contacts = data::load_contacts()?; let events = data::load_events()?; let mut offset = 0; for localpart in SYNC_IN { let index = accounts .iter() .position(|a| a.localpart == localpart) .ok_or_else(|| error::SeedError::Shape(format!("missing account {localpart}")))?; let layout = layout_for(localpart); let (email, password, account_id) = { let a = &accounts[index]; (a.email.clone(), a.password.clone(), a.account_id.clone()) }; let user = Jmap::connect(base_url, &email, &password)?; let end = (offset + layout.email_count).min(messages.len()); let slice = &messages[offset..end]; offset = end; let (c0, c1, e0, e1) = match localpart { "test1" => (0, contacts.len().min(3), 0, events.len().min(4)), "test2" => ( contacts.len().min(3), contacts.len().min(6), events.len().min(4), events.len().min(8), ), _ => ( contacts.len().min(6), contacts.len(), events.len().min(8), events.len(), ), }; let stats = seed::seed_account( &user, &account_id, &email, &layout, slice, &contacts[c0..c1], &events[e0..e1], )?; accounts[index].seeded = Some(stats); } Ok(Fixture { base_url: base_url.to_owned(), domain: DOMAIN.to_owned(), domain_id, admin_login: (ADMIN_USER.to_owned(), ADMIN_PASSWORD.to_owned()), accounts, }) }