Merge pull request 'feat&fix: replace maildev with mailpit and use util lib to interact with its HTTP API' (#99) from maildev-client into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Reviewed-on: #99
This commit is contained in:
Aravinth Manivannan 2024-09-13 17:38:56 +05:30
commit 750849b287
9 changed files with 543 additions and 506 deletions

View file

@ -4,6 +4,6 @@ export POSTGRES_DATABASE_URL="postgres://postgres:password@localhost:5432/postgr
export VANIKAM_email_USERNAME=admin
export VANIKAM_email_PASSWORD=password
export VANIKAM_email_SERVER_HOSTNAME=localhost:10025
export MAILDEV_URL=http://localhost:1080
export MAILPIT_URL=http://localhost:1080
export VANIKAM_email_FROM="Vanikam Info <info@vanikam.app>"
export VANIKAM_email_REPLY_TO="Vanikam Support <support@vanikam.app>"

View file

@ -6,9 +6,9 @@ steps:
environment:
- DATABASE_URL=postgres://postgres:password@database:5432/postgres
- VANIKAM_email_URL=smtp://admin:password@email:10025
- MAILDEV_URL=http://email:1080
- VANIKAM_meili_API_KEY=5c8eb5f46c148884fb64da09be211a18347fbba24435ca603adc9eba608ba66d
- VANIKAM_meili_URL=http://meilisearch:7700
- MAILPIT_URL=http://email:1080
commands:
# - curl -fsSL https://deb.nodesource.com/setup_16.x | bash - &&\
# - apt update && apt-get -y --no-install-recommends install nodejs tar gpg curl wget
@ -69,15 +69,15 @@ steps:
# secrets: [RELEASE_BOT_GPG_SIGNING_KEY, DUMBSERVE_PASSWORD, GPG_PASSWORD]
#
services:
email:
image: axllent/mailpit
environment:
- MP_SMTP_AUTH=admin:password
- MP_MAX_MESSAGES=5000
- MP_SMTP_AUTH_ALLOW_INSECURE=1
- MP_SMTP_BIND_ADDR=0.0.0.0:10025
- MP_SMTP_AUTH_ALLOW_INSECURE=true
- MP_UI_BIND_ADDR=0.0.0.0:1080
# email:
# image: axllent/mailpit
# environment:
# - MP_SMTP_AUTH=admin:password
# - MP_MAX_MESSAGES=5000
# - MP_SMTP_AUTH_ALLOW_INSECURE=1
# - MP_SMTP_BIND_ADDR=0.0.0.0:10025
# - MP_SMTP_AUTH_ALLOW_INSECURE=true
# - MP_UI_BIND_ADDR=0.0.0.0:1080
database:
@ -90,3 +90,11 @@ services:
environment:
- MEILI_ENV=development
- MEILI_MASTER_KEY=5c8eb5f46c148884fb64da09be211a18347fbba24435ca603adc9eba608ba66d
email:
image: axllent/mailpit
environment:
- MP_SMTP_BIND_ADDR=0.0.0.0:10025
- MP_UI_BIND_ADDR=0.0.0.0:1080
- MP_SMTP_AUTH_ACCEPT_ANY=true
- MP_SMTP_AUTH_ALLOW_INSECURE=true

796
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ edition = "2021"
[workspace]
exclude = ["utils/db-migrations"] #, "utils/cache-bust"]
memebers = ["."]
members = [".", "mailpit_client"]
[dependencies]
actix-identity = "0.7.1"
@ -40,3 +40,4 @@ validator = { version = "0.18.1", features = ["derive"] }
[dev-dependencies]
reqwest = { version = "0.12.4", features = ["json"] }
mailpit_client = { path = "./mailpit_client" }

View file

@ -3,16 +3,17 @@ version: "3"
services:
email:
image: axllent/mailpit
ports:
- 1080:1080
- 10025:10025
restart: always
container_name: vanigam-dash-maildev
network_mode: host
environment:
- MP_SMTP_AUTH=admin:password
- MP_MAX_MESSAGES=5000
- MP_SMTP_AUTH_ALLOW_INSECURE=1
- MP_SMTP_BIND_ADDR=0.0.0.0:10025
- MP_SMTP_AUTH_ALLOW_INSECURE=true
- MP_UI_BIND_ADDR=0.0.0.0:1080
- MP_SMTP_AUTH_ACCEPT_ANY=true
- MP_SMTP_AUTH_ALLOW_INSECURE=true
# - MAILDEV_SMTP_PORT=10025
# - MAILDEV_INCOMING_USER=admin
# - MAILDEV_INCOMING_PASS=password
postgres:
image: postgres:16.4

15
mailpit_client/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "mailpit_client"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.12.4", features = ["json"] }
serde = { version = "1.0.201", features = ["derive"] }
serde_json = "1.0.117"
actix-rt = "2.9.0"
derive-getters = "0.4.0"
derive_more = "0.99.17"
log = "0.4.21"
derive_builder = "0.20.0"
url = { version = "2.5.0", features = ["serde"] }

89
mailpit_client/src/lib.rs Normal file
View file

@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use derive_getters::Getters;
#[allow(unused_imports)]
use log::*;
#[allow(unused_imports)]
#[cfg(test)]
use println as info;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Getters, Builder)]
#[serde(rename_all = "PascalCase")]
pub struct MailPitAddress {
address: String,
name: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Getters, Builder)]
#[serde(rename_all = "PascalCase")]
pub struct MailPitEmail {
#[serde(rename = "ID")]
id: String,
from: MailPitAddress,
to: Vec<MailPitAddress>,
subject: String,
#[serde(rename = "Snippet")]
text: String,
html: Option<String>,
}
#[derive(Clone, Debug, Getters, Builder)]
pub struct MailPitHTTPClient {
#[builder(default = "Client::default()")]
client: Client,
url: Url,
}
impl MailPitHTTPClient {
pub async fn list_emails(&self) -> Vec<MailPitEmail> {
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
struct List {
messages: Vec<MailPitEmail>,
}
let mut u = self.url.clone();
u.set_path("/api/v1/messages");
info!("trying to fetch emails: {}", u.as_str());
let list: List = self
.client
.get(u.clone())
.send()
.await
.unwrap()
.json()
.await
.unwrap();
list.messages
}
pub async fn get_email_addressed_to(&self, email_address: &str) -> MailPitEmail {
self.list_emails()
.await
.drain(0..)
.find(|e| e.to.iter().any(|f| f.address == email_address))
.unwrap()
}
pub async fn delete_email(&self, email: MailPitEmail) {
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
struct DeletePayload {
#[serde(rename = "IDs")]
id: Vec<String>,
}
let mut u = self.url.clone();
u.set_path("/api/v1/messages/");
info!("Deleting email from: {:?}", email.from);
let payload = DeletePayload { id: vec![email.id] };
self.client.delete(u).json(&payload).send().await.unwrap();
}
}

View file

@ -38,67 +38,11 @@ impl AccountValidationLinkOutMailerPort for LettreMailer {
#[cfg(test)]
mod tests {
use super::*;
use reqwest::Client;
use url::Url;
use serde::{Deserialize, Serialize};
use mailpit_client::*;
//#[derive(Deserialize, Clone)]
//struct MaildevAddress {
// address: String,
// name: String,
//}
//#[derive(Deserialize, Clone)]
//struct MaildevEmail {
// id: String,
// from: Vec<MaildevAddress>,
// to: Vec<MaildevAddress>,
// subject: String,
// text: String,
// html: Option<String>,
//}
#[derive(Deserialize, Clone)]
struct MailPitAddress {
#[serde(rename = "Address")]
address: String,
#[serde(rename = "Name")]
name: String,
}
#[derive(Deserialize, Clone)]
pub struct MailpitSummary {
#[serde(rename = "ID")]
id: String,
#[serde(rename = "To")]
to: Vec<MailPitAddress>,
}
#[derive(Deserialize, Clone)]
pub struct MailpitEmail {
#[serde(rename = "From")]
from: MailPitAddress,
#[serde(rename = "To")]
to: Vec<MailPitAddress>,
#[serde(rename = "ReplyTo")]
reply_to: Vec<MailPitAddress>,
#[serde(rename = "Subject")]
subject: String,
#[serde(rename = "Text")]
text: String,
#[serde(rename = "HTML")]
html: String,
}
#[derive(Deserialize, Clone)]
pub struct MailpitListEmails {
messages: Vec<MailpitSummary>,
}
#[derive(Serialize, Clone)]
pub struct MailpitDeleteEmail {
IDs: Vec<String>,
}
use serde::Deserialize;
#[actix_rt::test]
async fn test_mailer_account_validation_link() {
@ -113,38 +57,22 @@ mod tests {
.await
.unwrap();
let c = Client::default();
let maildev_url =
std::env::var("MAILDEV_URL").expect("Please set maildev instance URL in MAILDEV_URL");
let mut u = Url::parse(&maildev_url).unwrap();
u.set_path("/api/v1/messages");
let mailpit_emails: MailpitListEmails =
c.get(u.clone()).send().await.unwrap().json().await.unwrap();
let mailpit_summary = mailpit_emails
.messages
.iter()
.find(|e| e.to.iter().any(|f| f.address == email))
let mailpit_url =
std::env::var("MAILPIT_URL").expect("Please set mailpit instance URL in MAILPIT_URL");
let mc = MailPitHTTPClientBuilder::default()
.url(Url::parse(&mailpit_url).unwrap())
.build()
.unwrap();
u.set_path(&format!("/api/v1/message/{}", mailpit_summary.id));
let mailpit_email: MailpitEmail =
c.get(u.clone()).send().await.unwrap().json().await.unwrap();
let mailpit_email = mc.get_email_addressed_to(email).await;
assert!(mailpit_email.text.contains(validation_secret));
assert!(mailpit_email.text.contains(username));
assert!(mailpit_email.text().contains(validation_secret));
assert!(mailpit_email.text().contains(username));
assert!(mailpit_email
.to
.to()
.iter()
.any(|t| t.address == email && t.name == username));
.any(|t| t.address() == email && t.name() == username));
u.set_path("/api/v1/messages");
let payload = MailpitDeleteEmail {
IDs: vec![mailpit_summary.id.clone()],
};
c.delete(u).json(&payload).send().await.unwrap();
mc.delete_email(mailpit_email).await;
}
}

View file

@ -3,7 +3,8 @@ name = "db-migrations"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
[dependencies]
actix-rt = "2.9.0"