changes for version 0.1.5 and release of version 0.1.5 #23
5 changed files with 123 additions and 44 deletions
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
commit
3c5550376f
|
|
@ -3,3 +3,7 @@
|
||||||
[login_provider]
|
[login_provider]
|
||||||
type = 'Simple'
|
type = 'Simple'
|
||||||
file_path = 'config/login.toml'
|
file_path = 'config/login.toml'
|
||||||
|
|
||||||
|
[email]
|
||||||
|
from = "jobs@localhost"
|
||||||
|
subject = "Test"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use crate::error::default_error_response;
|
||||||
|
use actix_web::body::BoxBody;
|
||||||
use actix_web::error::UrlGenerationError;
|
use actix_web::error::UrlGenerationError;
|
||||||
use actix_web::ResponseError;
|
use actix_web::{HttpResponse, ResponseError};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|
@ -14,10 +16,8 @@ pub(crate) enum SaveError {
|
||||||
Serialize(#[from] toml::ser::Error),
|
Serialize(#[from] toml::ser::Error),
|
||||||
#[error("Could not generate url for confirmation link: {0}")]
|
#[error("Could not generate url for confirmation link: {0}")]
|
||||||
Url(#[from] UrlGenerationError),
|
Url(#[from] UrlGenerationError),
|
||||||
#[error("Construction of confirmation E-mail failed!")]
|
#[error("Could not send Confirmation E-Mail: {0}")]
|
||||||
Email(#[from] lettre::error::Error),
|
Email(#[from] EmailError),
|
||||||
#[error("Sending of confirmation E-mail failed!")]
|
|
||||||
SendMail(#[from] lettre::transport::sendmail::Error),
|
|
||||||
#[error("The Runtime encountered an error!")]
|
#[error("The Runtime encountered an error!")]
|
||||||
Runtime(#[from] tokio::task::JoinError),
|
Runtime(#[from] tokio::task::JoinError),
|
||||||
}
|
}
|
||||||
|
|
@ -44,10 +44,48 @@ impl ResponseError for SaveError {
|
||||||
| SaveError::IO(_)
|
| SaveError::IO(_)
|
||||||
| SaveError::Serialize(_)
|
| SaveError::Serialize(_)
|
||||||
| SaveError::Url(_)
|
| SaveError::Url(_)
|
||||||
| SaveError::Runtime(_)
|
| SaveError::Runtime(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
| SaveError::SendMail(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
SaveError::AlreadyExists => StatusCode::CONFLICT,
|
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,
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,20 @@ use log::{debug, error, warn};
|
||||||
use rand::distributions::DistString;
|
use rand::distributions::DistString;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::auth::User;
|
use crate::auth::User;
|
||||||
use crate::error::{MultipartFieldError, PresentationError};
|
use crate::error::{MultipartFieldError, PresentationError};
|
||||||
use crate::job_offers::error::SaveError;
|
use crate::job_offers::error::{EmailError, SaveError};
|
||||||
use crate::job_offers::{
|
use crate::job_offers::{
|
||||||
Attachment, ConfirmationStatus, JobOffer, JobOfferStatus, JobOffers, Link, MutBorrowedJobOffer,
|
Attachment, ConfirmationStatus, JobOffer, JobOfferStatus, JobOffers, Link, MutBorrowedJobOffer,
|
||||||
ReviewStatus,
|
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::create::multipart_form::{multi_field, multi_file, once_field};
|
||||||
use crate::route::job_offer::error::SubmissionError;
|
use crate::route::job_offer::error::SubmissionError;
|
||||||
use crate::route::HTML_CONTENT;
|
use crate::route::HTML_CONTENT;
|
||||||
use crate::server_config::ServerConfig;
|
use crate::server_config::{EmailConfig, ServerConfig};
|
||||||
use crate::{template, SubmissionLimiter};
|
use crate::{template, SubmissionLimiter};
|
||||||
|
|
||||||
mod multipart_form;
|
mod multipart_form;
|
||||||
|
|
@ -113,7 +115,7 @@ pub(crate) async fn create_joboffer_post(
|
||||||
debug!("validation successful, saving submitted job offer");
|
debug!("validation successful, saving submitted job offer");
|
||||||
|
|
||||||
let created_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,
|
Ok(offer) => offer,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(lease) = submission_lease {
|
if let Some(lease) = submission_lease {
|
||||||
|
|
@ -169,13 +171,12 @@ pub(crate) struct JobOfferSubmitForm {
|
||||||
|
|
||||||
pub(crate) async fn create_job_offer<'data, 'config>(
|
pub(crate) async fn create_job_offer<'data, 'config>(
|
||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
|
hb: &Handlebars<'_>,
|
||||||
job_offer_form: JobOfferSubmitForm,
|
job_offer_form: JobOfferSubmitForm,
|
||||||
offers: &'data JobOffers,
|
offers: &'data JobOffers,
|
||||||
config: &'config ServerConfig,
|
config: &'config ServerConfig,
|
||||||
user: Option<&User>,
|
user: Option<&User>,
|
||||||
) -> Result<MutBorrowedJobOffer<'data, 'static, 'config>, SaveError> {
|
) -> Result<MutBorrowedJobOffer<'data, 'static, 'config>, SaveError> {
|
||||||
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
|
|
||||||
|
|
||||||
let now_date = crate::util::now();
|
let now_date = crate::util::now();
|
||||||
|
|
||||||
let token: String =
|
let token: String =
|
||||||
|
|
@ -217,44 +218,72 @@ pub(crate) async fn create_job_offer<'data, 'config>(
|
||||||
|
|
||||||
if !skip_confirmation {
|
if !skip_confirmation {
|
||||||
if let Some(email_config) = &config.config.email {
|
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 confirm_url = req.url_for(JOBOFFER_CONFIRM_ROUTE, &[created_offer.id(), &token])?;
|
||||||
|
|
||||||
let message = lettre::Message::builder()
|
send_confirmation_email(
|
||||||
.from(email_config.from.to_owned())
|
hb,
|
||||||
.to(to_mailbox)
|
job_offer_form.contact,
|
||||||
.subject(&email_config.subject)
|
email_config,
|
||||||
.singlepart(SinglePart::plain(format!(
|
&EmailData {
|
||||||
include_str!("confirmation_email_template.txt"),
|
confirmation_link: confirm_url,
|
||||||
confirm_url
|
},
|
||||||
)))?;
|
)
|
||||||
lettre::AsyncSendmailTransport::new().send(message).await?;
|
.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(created_offer)
|
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 {
|
impl JobOfferSubmitForm {
|
||||||
/// Convert the Multipart struct representing multipart form-data
|
/// Convert the Multipart struct representing multipart form-data
|
||||||
/// into structured form data
|
/// into structured form data
|
||||||
|
|
|
||||||
|
|
@ -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 JOBOFFER_REJECT_SUBMISSION_SUCCESS: &str = "job_offer/submission-rejected-success";
|
||||||
pub(crate) const AUTH_LOGIN: &str = "auth/login";
|
pub(crate) const AUTH_LOGIN: &str = "auth/login";
|
||||||
pub(crate) const LICENCES: &str = "licenses";
|
pub(crate) const LICENCES: &str = "licenses";
|
||||||
|
|
||||||
|
pub const EMAIL_PLAIN: &'static str = "email/plaintext";
|
||||||
|
|
|
||||||
|
|
@ -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.
|
Bitte überprüfen Sie die Stellenausschreibung unter dem folgenden Link.
|
||||||
|
|
||||||
{}
|
{{{confirmation_link}}}
|
||||||
|
|
||||||
Dort können sie die Stellenausschreibung bestätigen oder zurückziehen.
|
Dort können sie die Stellenausschreibung bestätigen oder zurückziehen.
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue