Merge pull request 'add mail' (#108) from mail into staging
Reviewed-on: #108
This commit is contained in:
commit
99d5328831
216
Cargo.lock
generated
216
Cargo.lock
generated
@ -333,6 +333,16 @@ dependencies = [
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@ -366,6 +376,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
@ -554,6 +574,22 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-encoding"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email_address"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
@ -664,6 +700,21 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@ -948,6 +999,17 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.11"
|
||||
@ -1186,6 +1248,31 @@ dependencies = [
|
||||
"spin 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lettre"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a48c2e9831b370bc2d7233c2620298c45f3a158ed6b4b8d7416b2ada5a268fd8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chumsky",
|
||||
"email-encoding",
|
||||
"email_address",
|
||||
"fastrand",
|
||||
"futures-util",
|
||||
"hostname",
|
||||
"httpdate",
|
||||
"idna",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"quoted_printable",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
@ -1246,6 +1333,12 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@ -1333,6 +1426,24 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
@ -1471,6 +1582,50 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
@ -1732,6 +1887,15 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-rust-locales"
|
||||
version = "0.7.0"
|
||||
@ -1747,6 +1911,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quoted_printable"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@ -1968,6 +2138,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"futures",
|
||||
"ics",
|
||||
"lettre",
|
||||
"log",
|
||||
"rocket",
|
||||
"rocket_dyn_templates",
|
||||
@ -2067,6 +2238,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@ -2089,6 +2269,29 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
@ -2473,6 +2676,19 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "state"
|
||||
version = "0.6.0"
|
||||
|
@ -22,3 +22,5 @@ chrono-tz = "0.8"
|
||||
tera = { version = "1.18", features = ["date-locale"], optional = true}
|
||||
ics = "0.5"
|
||||
futures = "0.3"
|
||||
lettre = "0.11"
|
||||
|
||||
|
@ -2,3 +2,4 @@
|
||||
secret_key = "/NtVGizglEoyoxBLzsRDWTy4oAG1qDw4J4O+CWJSv+fypD7W9sam8hUY4j90EZsbZk8wEradS5zBoWtWKi3k8w=="
|
||||
rss_key = "rss-key-for-ci"
|
||||
limits = { file = "10 MiB", data-form = "10 MiB"}
|
||||
smtp_pw = "8kIjlLH79Ky6D3jQ"
|
||||
|
65
src/model/mail.rs
Normal file
65
src/model/mail.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use std::error::Error;
|
||||
|
||||
use lettre::{
|
||||
message::{
|
||||
header::{self, ContentType},
|
||||
MultiPart, SinglePart,
|
||||
},
|
||||
transport::smtp::authentication::Credentials,
|
||||
Message, SmtpTransport, Transport,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::tera::admin::mail::MailToSend;
|
||||
|
||||
use super::role::Role;
|
||||
|
||||
pub struct Mail {}
|
||||
|
||||
impl Mail {
|
||||
pub async fn send(db: &SqlitePool, data: MailToSend<'_>, smtp_pw: String) -> bool {
|
||||
let mut email = Message::builder()
|
||||
.from(
|
||||
"ASKÖ Ruderverein Donau Linz <no-reply@rudernlinz.at>"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.reply_to(
|
||||
"ASKÖ Ruderverein Donau Linz <info@rudernlinz.at>"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.to("ASKÖ Ruderverein Donau Linz <no-reply@rudernlinz.at>"
|
||||
.parse()
|
||||
.unwrap());
|
||||
let role = Role::find_by_id(db, data.role_id).await.unwrap();
|
||||
for rec in role.mails_from_role(db).await {
|
||||
let splitted = rec.split(',');
|
||||
for single_rec in splitted {
|
||||
email = email.bcc(single_rec.parse().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle attachments
|
||||
|
||||
let email = email
|
||||
.subject(data.subject)
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(String::from(data.body))
|
||||
.unwrap();
|
||||
|
||||
let creds = Credentials::new("no-reply@rudernlinz.at".to_owned(), smtp_pw);
|
||||
|
||||
let mailer = SmtpTransport::relay("mail.your-server.de")
|
||||
.unwrap()
|
||||
.credentials(creds)
|
||||
.build();
|
||||
|
||||
// Send the email
|
||||
match mailer.send(&email) {
|
||||
Ok(_) => return true,
|
||||
Err(e) => println!("{:?}", e.source()),
|
||||
};
|
||||
false
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ pub mod location;
|
||||
pub mod log;
|
||||
pub mod logbook;
|
||||
pub mod logtype;
|
||||
pub mod mail;
|
||||
pub mod planned_event;
|
||||
pub mod role;
|
||||
pub mod rower;
|
||||
|
@ -14,4 +14,32 @@ impl Role {
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_by_id(db: &SqlitePool, name: i32) -> Option<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name
|
||||
FROM role
|
||||
WHERE id like ?
|
||||
",
|
||||
name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn mails_from_role(&self, db: &SqlitePool) -> Vec<String> {
|
||||
let query = format!(
|
||||
"SELECT u.mail
|
||||
FROM user u
|
||||
JOIN user_role ur ON u.id = ur.user_id
|
||||
JOIN role r ON ur.role_id = r.id
|
||||
WHERE r.id = {}",
|
||||
self.id
|
||||
);
|
||||
|
||||
sqlx::query_scalar(&query).fetch_all(db).await.unwrap()
|
||||
}
|
||||
}
|
||||
|
64
src/tera/admin/mail.rs
Normal file
64
src/tera/admin/mail.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use rocket::form::Form;
|
||||
use rocket::fs::TempFile;
|
||||
use rocket::response::{Flash, Redirect};
|
||||
use rocket::{get, request::FlashMessage, routes, Route, State};
|
||||
use rocket::{post, FromForm};
|
||||
use rocket_dyn_templates::{tera::Context, Template};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::model::mail::Mail;
|
||||
use crate::model::role::Role;
|
||||
use crate::model::user::AdminUser;
|
||||
use crate::model::user::UserWithRoles;
|
||||
use crate::tera::Config;
|
||||
|
||||
#[get("/mail")]
|
||||
async fn index(
|
||||
db: &State<SqlitePool>,
|
||||
admin: AdminUser,
|
||||
flash: Option<FlashMessage<'_>>,
|
||||
) -> Template {
|
||||
let mut context = Context::new();
|
||||
if let Some(msg) = flash {
|
||||
context.insert("flash", &msg.into_inner());
|
||||
}
|
||||
let roles = Role::all(db).await;
|
||||
|
||||
context.insert(
|
||||
"loggedin_user",
|
||||
&UserWithRoles::from_user(admin.user, db).await,
|
||||
);
|
||||
context.insert("roles", &roles);
|
||||
|
||||
Template::render("admin/mail", context.into_json())
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug)]
|
||||
pub struct MailToSend<'a> {
|
||||
pub(crate) role_id: i32,
|
||||
pub(crate) subject: String,
|
||||
pub(crate) body: String,
|
||||
pub(crate) files: Vec<TempFile<'a>>,
|
||||
}
|
||||
|
||||
#[post("/mail", data = "<data>")]
|
||||
async fn update(
|
||||
db: &State<SqlitePool>,
|
||||
data: Form<MailToSend<'_>>,
|
||||
config: &State<Config>,
|
||||
_admin: AdminUser,
|
||||
) -> Flash<Redirect> {
|
||||
let d = data.into_inner();
|
||||
if Mail::send(db, d, config.smtp_pw.clone()).await {
|
||||
return Flash::success(Redirect::to("/admin/mail"), "Mail versendet");
|
||||
} else {
|
||||
return Flash::error(Redirect::to("/admin/mail"), "Fehler");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![index, update]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
@ -7,6 +7,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub mod boat;
|
||||
pub mod mail;
|
||||
pub mod planned_event;
|
||||
pub mod user;
|
||||
|
||||
@ -28,6 +29,7 @@ pub fn routes() -> Vec<Route> {
|
||||
let mut ret = Vec::new();
|
||||
ret.append(&mut user::routes());
|
||||
ret.append(&mut boat::routes());
|
||||
ret.append(&mut mail::routes());
|
||||
ret.append(&mut planned_event::routes());
|
||||
ret.append(&mut routes![rss, show_rss]);
|
||||
ret
|
||||
|
@ -206,6 +206,7 @@ fn unauthorized_error() -> Redirect {
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Config {
|
||||
rss_key: String,
|
||||
smtp_pw: String,
|
||||
}
|
||||
|
||||
pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||
|
27
templates/admin/mail.html.tera
Normal file
27
templates/admin/mail.html.tera
Normal file
@ -0,0 +1,27 @@
|
||||
{% import "includes/macros" as macros %}
|
||||
{% import "includes/forms/boat" as boat %}
|
||||
|
||||
{% extends "base" %}
|
||||
|
||||
{% block content %}
|
||||
{% if flash %}
|
||||
{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}
|
||||
{% endif %}
|
||||
|
||||
<div class="max-w-screen-lg w-full">
|
||||
<h1 class="h1">Mail</h1>
|
||||
<form action="/admin/mail" method="post" enctype="multipart/form-data">
|
||||
<select name="role_id">
|
||||
{% for role in roles %}
|
||||
<option value="{{ role.id }}">{{ role.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" name="subject" />
|
||||
<textarea name="body" rows="4" cols="50"></textarea>
|
||||
<input type="file" name="files" multiple />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user