changes for version 0.1.5 and release of version 0.1.5 #23
12 changed files with 149 additions and 52 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1204,7 +1204,7 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobboerse"
|
name = "jobboerse"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "jobboerse"
|
name = "jobboerse"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.58"
|
rust-version = "1.58"
|
||||||
repository = "https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse"
|
repository = "https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse"
|
||||||
|
|
|
||||||
10
Changelog.md
10
Changelog.md
|
|
@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.1.5] (2022-05-26)
|
||||||
|
|
||||||
|
### Change
|
||||||
|
- some display/style changes for job offer entries
|
||||||
|
- the confirmation email now uses a handlebar template instead of a compiletime format constant string
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
- /summary should be accessible for anonymous visitors
|
||||||
|
|
||||||
## [0.1.4] (2022-05-25)
|
## [0.1.4] (2022-05-25)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
@ -57,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Overview Page of Dependency licenses
|
- Overview Page of Dependency licenses
|
||||||
|
|
||||||
[Unreleased]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/main
|
[Unreleased]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/main
|
||||||
|
[0.1.5]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.5
|
||||||
[0.1.4]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.4
|
[0.1.4]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.4
|
||||||
[0.1.3]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.3
|
[0.1.3]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.3
|
||||||
[0.1.2]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.2
|
[0.1.2]: https://www.fs-infmath.uni-kiel.de/git/FS-InfMath/Jobboerse/src/version-0.1.2
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
2
dist/arch/PKGBUILD
vendored
2
dist/arch/PKGBUILD
vendored
|
|
@ -9,7 +9,7 @@ _reponame=Jobboerse
|
||||||
_pkgname="${_reponame,,}"
|
_pkgname="${_reponame,,}"
|
||||||
_features=()
|
_features=()
|
||||||
pkgname="${_reponame,,}"
|
pkgname="${_reponame,,}"
|
||||||
pkgver=0.1.4
|
pkgver=0.1.5
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="FS-InfMath Job-Offer Page"
|
pkgdesc="FS-InfMath Job-Offer Page"
|
||||||
arch=('x86_64') # Other architectures may work
|
arch=('x86_64') # Other architectures may work
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,10 +148,10 @@ pub(crate) async fn summary(
|
||||||
session: Session,
|
session: Session,
|
||||||
offers: web::Data<JobOffers>,
|
offers: web::Data<JobOffers>,
|
||||||
) -> Result<HttpResponse, actix_web::Error> {
|
) -> Result<HttpResponse, actix_web::Error> {
|
||||||
let user = auth::User::current(&session)?;
|
let user = auth::User::current(&session).ok();
|
||||||
let previews = {
|
let previews = {
|
||||||
let guard = offers.get_offers().await;
|
let guard = offers.get_offers().await;
|
||||||
crate::job_offers::job_data(&req, guard, Some(&user))
|
crate::job_offers::job_data(&req, guard, user.as_ref())
|
||||||
};
|
};
|
||||||
let data = json!(previews);
|
let data = json!(previews);
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
|
|
|
||||||
|
|
@ -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,18 +218,47 @@ 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])?;
|
||||||
|
|
||||||
|
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()
|
let message = lettre::Message::builder()
|
||||||
.from(email_config.from.to_owned())
|
.from(email_config.from.to_owned())
|
||||||
.to(to_mailbox)
|
.to(to_mailbox)
|
||||||
.subject(&email_config.subject)
|
.subject(&email_config.subject)
|
||||||
.singlepart(SinglePart::plain(format!(
|
.singlepart(SinglePart::plain(email_body))
|
||||||
include_str!("confirmation_email_template.txt"),
|
.map_err(EmailError::from)?;
|
||||||
confirm_url
|
|
||||||
)))?;
|
|
||||||
lettre::AsyncSendmailTransport::new().send(message).await?;
|
lettre::AsyncSendmailTransport::new().send(message).await?;
|
||||||
|
|
||||||
let message = lettre::Message::builder()
|
let message = lettre::Message::builder()
|
||||||
|
|
@ -236,7 +266,8 @@ pub(crate) async fn create_job_offer<'data, 'config>(
|
||||||
.to(email_config.from.to_owned())
|
.to(email_config.from.to_owned())
|
||||||
.subject(&email_config.subject)
|
.subject(&email_config.subject)
|
||||||
.singlepart(SinglePart::plain(
|
.singlepart(SinglePart::plain(
|
||||||
"Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!".to_owned()
|
"Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!"
|
||||||
|
.to_owned(),
|
||||||
));
|
));
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
|
|
@ -249,10 +280,8 @@ pub(crate) async fn create_job_offer<'data, 'config>(
|
||||||
warn!("Failed to construct reminder {}", err)
|
warn!("Failed to construct reminder {}", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(created_offer)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobOfferSubmitForm {
|
impl JobOfferSubmitForm {
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,14 @@ main {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.centered-text {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic-text {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 5px;
|
grid-gap: 5px;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<div class="joboffer-index-entry-content {{#if user}}{{job_offer.status}}{{/if}}">
|
<div class="joboffer-index-entry-content {{#if user}}{{job_offer.status}}{{/if}}">
|
||||||
<h2 class="joboffer-title centered">{{job_offer.title}}</h2>
|
<h2 class="joboffer-title centered centered-text">{{job_offer.title}}</h2>
|
||||||
<div class="centered">
|
<div class="joboffer-offering-party centered centered-text italic-text">
|
||||||
Stellenanzeige von {{job_offer.offering_party}}
|
{{job_offer.offering_party}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue