changes for version 0.1.5 and release of version 0.1.5 #23

Merged
ben merged 5 commits from ben/Jobboerse:main into main 2022-05-26 18:46:18 +02:00
5 changed files with 123 additions and 44 deletions
Showing only changes of commit 3c5550376f - Show all commits

move confirmation email template to a template file

* this allows for easier changes to the template, but also introduces a new source for runtime errors
* split the sending of the confirmation email of into a separate function
Bennet Bleßmann 2022-05-26 18:39:54 +02:00 committed by Bennet Bleßmann
Signed by: ben
GPG key ID: 3BE1A1A3CBC3CF99

View file

@ -3,3 +3,7 @@
[login_provider]
type = 'Simple'
file_path = 'config/login.toml'
[email]
from = "jobs@localhost"
subject = "Test"

View file

@ -1,5 +1,7 @@
use crate::error::default_error_response;
use actix_web::body::BoxBody;
use actix_web::error::UrlGenerationError;
use actix_web::ResponseError;
use actix_web::{HttpResponse, ResponseError};
use http::StatusCode;
#[derive(thiserror::Error, Debug)]
@ -14,10 +16,8 @@ pub(crate) enum SaveError {
Serialize(#[from] toml::ser::Error),
#[error("Could not generate url for confirmation link: {0}")]
Url(#[from] UrlGenerationError),
#[error("Construction of confirmation E-mail failed!")]
Email(#[from] lettre::error::Error),
#[error("Sending of confirmation E-mail failed!")]
SendMail(#[from] lettre::transport::sendmail::Error),
#[error("Could not send Confirmation E-Mail: {0}")]
Email(#[from] EmailError),
#[error("The Runtime encountered an error!")]
Runtime(#[from] tokio::task::JoinError),
}
@ -44,10 +44,48 @@ impl ResponseError for SaveError {
| SaveError::IO(_)
| SaveError::Serialize(_)
| SaveError::Url(_)
| SaveError::Runtime(_)
| SaveError::SendMail(_) => StatusCode::INTERNAL_SERVER_ERROR,
| SaveError::Runtime(_) => StatusCode::INTERNAL_SERVER_ERROR,
SaveError::AlreadyExists => StatusCode::CONFLICT,
SaveError::Email(_) => StatusCode::BAD_REQUEST,
SaveError::Email(inner) => inner.status_code(),
}
}
fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code();
match self {
SaveError::AlreadyExists => default_error_response(self, status_code),
SaveError::IO(inner) => default_error_response(inner, status_code),
SaveError::Persist(inner) => default_error_response(inner, status_code),
SaveError::Serialize(inner) => default_error_response(inner, status_code),
SaveError::Url(inner) => default_error_response(inner, status_code),
SaveError::Email(inner) => inner.error_response(),
SaveError::Runtime(inner) => default_error_response(inner, status_code),
}
}
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum EmailError {
#[error("Construction of confirmation E-mail failed!")]
Email(#[from] lettre::error::Error),
#[error("Sending of confirmation E-mail failed!")]
SendMail(#[from] lettre::transport::sendmail::Error),
#[error("Error in Confirmation E-Mail template!")]
Template(#[from] handlebars::RenderError),
}
impl ResponseError for EmailError {
fn status_code(&self) -> StatusCode {
match self {
EmailError::Email(_) => StatusCode::BAD_REQUEST,
EmailError::SendMail(_) => StatusCode::INTERNAL_SERVER_ERROR,
EmailError::Template(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code();
match self {
EmailError::Email(inner) => default_error_response(inner, status_code),
EmailError::SendMail(inner) => default_error_response(inner, status_code),
EmailError::Template(inner) => default_error_response(inner, status_code),
}
}
}
@ -64,4 +102,10 @@ impl ResponseError for DeleteError {
DeleteError::IO(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code();
match self {
DeleteError::IO(inner) => default_error_response(inner, status_code),
}
}
}

View file

@ -13,18 +13,20 @@ use log::{debug, error, warn};
use rand::distributions::DistString;
use serde_json::json;
use tempfile::NamedTempFile;
use url::Url;
use crate::auth::User;
use crate::error::{MultipartFieldError, PresentationError};
use crate::job_offers::error::SaveError;
use crate::job_offers::error::{EmailError, SaveError};
use crate::job_offers::{
Attachment, ConfirmationStatus, JobOffer, JobOfferStatus, JobOffers, Link, MutBorrowedJobOffer,
ReviewStatus,
};
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
use crate::route::job_offer::create::multipart_form::{multi_field, multi_file, once_field};
use crate::route::job_offer::error::SubmissionError;
use crate::route::HTML_CONTENT;
use crate::server_config::ServerConfig;
use crate::server_config::{EmailConfig, ServerConfig};
use crate::{template, SubmissionLimiter};
mod multipart_form;
@ -113,7 +115,7 @@ pub(crate) async fn create_joboffer_post(
debug!("validation successful, saving submitted job offer");
let created_offer =
match create_job_offer(&req, job_offer_form, &offers, &config, user.as_ref()).await {
match create_job_offer(&req, &hb, job_offer_form, &offers, &config, user.as_ref()).await {
Ok(offer) => offer,
Err(err) => {
if let Some(lease) = submission_lease {
@ -169,13 +171,12 @@ pub(crate) struct JobOfferSubmitForm {
pub(crate) async fn create_job_offer<'data, 'config>(
req: &HttpRequest,
hb: &Handlebars<'_>,
job_offer_form: JobOfferSubmitForm,
offers: &'data JobOffers,
config: &'config ServerConfig,
user: Option<&User>,
) -> Result<MutBorrowedJobOffer<'data, 'static, 'config>, SaveError> {
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
let now_date = crate::util::now();
let token: String =
@ -217,44 +218,72 @@ pub(crate) async fn create_job_offer<'data, 'config>(
if !skip_confirmation {
if let Some(email_config) = &config.config.email {
let to_mailbox = Mailbox::new(None, job_offer_form.contact);
let confirm_url = req.url_for(JOBOFFER_CONFIRM_ROUTE, &[created_offer.id(), &token])?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(to_mailbox)
.subject(&email_config.subject)
.singlepart(SinglePart::plain(format!(
include_str!("confirmation_email_template.txt"),
confirm_url
)))?;
lettre::AsyncSendmailTransport::new().send(message).await?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(email_config.from.to_owned())
.subject(&email_config.subject)
.singlepart(SinglePart::plain(
"Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!".to_owned()
));
match message {
Ok(msg) => {
if let Err(err) = lettre::AsyncSendmailTransport::new().send(msg).await {
warn!("Failed to send remainder {}", err);
}
}
Err(err) => {
warn!("Failed to construct reminder {}", err)
}
}
send_confirmation_email(
hb,
job_offer_form.contact,
email_config,
&EmailData {
confirmation_link: confirm_url,
},
)
.await?;
}
}
Ok(created_offer)
}
#[derive(serde::Serialize)]
struct EmailData {
#[serde(serialize_with = "crate::route::url_as_string")]
confirmation_link: Url,
}
async fn send_confirmation_email(
hb: &Handlebars<'_>,
contact_address: Address,
email_config: &EmailConfig,
email_data: &EmailData,
) -> Result<(), EmailError> {
let to_mailbox = Mailbox::new(None, contact_address);
let email_body = hb
.render(template::EMAIL_PLAIN, &email_data)
.map_err(EmailError::from)?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(to_mailbox)
.subject(&email_config.subject)
.singlepart(SinglePart::plain(email_body))
.map_err(EmailError::from)?;
lettre::AsyncSendmailTransport::new().send(message).await?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(email_config.from.to_owned())
.subject(&email_config.subject)
.singlepart(SinglePart::plain(
"Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!"
.to_owned(),
));
match message {
Ok(msg) => {
if let Err(err) = lettre::AsyncSendmailTransport::new().send(msg).await {
warn!("Failed to send remainder {}", err);
}
}
Err(err) => {
warn!("Failed to construct reminder {}", err)
}
}
Ok(())
}
impl JobOfferSubmitForm {
/// Convert the Multipart struct representing multipart form-data
/// into structured form data

View file

@ -7,3 +7,5 @@ pub(crate) const JOBOFFER_CONFIRM_SUBMISSION_SUCCESS: &str = "job_offer/submissi
pub(crate) const JOBOFFER_REJECT_SUBMISSION_SUCCESS: &str = "job_offer/submission-rejected-success";
pub(crate) const AUTH_LOGIN: &str = "auth/login";
pub(crate) const LICENCES: &str = "licenses";
pub const EMAIL_PLAIN: &'static str = "email/plaintext";

View file

@ -4,7 +4,7 @@ Es wurde eine Stellenausschreibung mit dieser E-Mail Address als Kontakt-Address
Bitte überprüfen Sie die Stellenausschreibung unter dem folgenden Link.
{}
{{{confirmation_link}}}
Dort können sie die Stellenausschreibung bestätigen oder zurückziehen.