[Draft] Implement Early deletion by Submitter #37 #38
5 changed files with 119 additions and 25 deletions
don't remove confirmation token after confirmation
this way it can be used for early deletion rename it to submitter token in the process
commit
70797cc298
|
|
@ -110,6 +110,13 @@ pub(crate) struct Link {
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Hash)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Hash)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ConfirmationStatus {
|
pub enum ConfirmationStatus {
|
||||||
|
AwaitingConfirmation,
|
||||||
|
Confirmed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Hash)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum V1ConfirmationStatus {
|
||||||
AwaitingConfirmation { token: String },
|
AwaitingConfirmation { token: String },
|
||||||
Confirmed,
|
Confirmed,
|
||||||
}
|
}
|
||||||
|
|
@ -128,16 +135,47 @@ pub enum ReviewStatus {
|
||||||
pub struct JobOfferStatus {
|
pub struct JobOfferStatus {
|
||||||
review_status: ReviewStatus,
|
review_status: ReviewStatus,
|
||||||
confirmation_status: ConfirmationStatus,
|
confirmation_status: ConfirmationStatus,
|
||||||
|
#[serde(default)]
|
||||||
|
submitter_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<V1JobOfferStatus> for JobOfferStatus {
|
||||||
|
fn from(
|
||||||
|
V1JobOfferStatus {
|
||||||
|
review_status,
|
||||||
|
confirmation_status,
|
||||||
|
}: V1JobOfferStatus,
|
||||||
|
) -> Self {
|
||||||
|
let (confirmation_status, token) = match confirmation_status {
|
||||||
|
V1ConfirmationStatus::AwaitingConfirmation { token } => {
|
||||||
|
(ConfirmationStatus::AwaitingConfirmation, Some(token))
|
||||||
|
}
|
||||||
|
V1ConfirmationStatus::Confirmed => (ConfirmationStatus::Confirmed, None),
|
||||||
|
};
|
||||||
|
JobOfferStatus {
|
||||||
|
review_status,
|
||||||
|
confirmation_status,
|
||||||
|
submitter_token: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Hash)]
|
||||||
|
pub struct V1JobOfferStatus {
|
||||||
|
review_status: ReviewStatus,
|
||||||
|
confirmation_status: V1ConfirmationStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobOfferStatus {
|
impl JobOfferStatus {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
review_status: ReviewStatus,
|
review_status: ReviewStatus,
|
||||||
confirmation_status: ConfirmationStatus,
|
confirmation_status: ConfirmationStatus,
|
||||||
|
submitter_token: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
review_status,
|
review_status,
|
||||||
confirmation_status,
|
confirmation_status,
|
||||||
|
submitter_token: Some(submitter_token),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,15 +193,12 @@ impl JobOfferStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_confirmation_token(&self, presented_token: &str) -> bool {
|
pub fn check_submitter_token(&self, presented_token: &str) -> bool {
|
||||||
match &self.confirmation_status {
|
self.submitter_token.as_deref() == Some(presented_token)
|
||||||
ConfirmationStatus::AwaitingConfirmation { token } => token == presented_token,
|
|
||||||
ConfirmationStatus::Confirmed => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_as_confirmed(&mut self, presented_token: &str) -> Result<(), ()> {
|
pub fn mark_as_confirmed(&mut self, presented_token: &str) -> Result<(), ()> {
|
||||||
if self.check_confirmation_token(presented_token) {
|
if self.check_submitter_token(presented_token) {
|
||||||
self.confirmation_status = ConfirmationStatus::Confirmed;
|
self.confirmation_status = ConfirmationStatus::Confirmed;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -177,6 +212,7 @@ impl JobOfferStatus {
|
||||||
JobOfferStatus {
|
JobOfferStatus {
|
||||||
review_status: ReviewStatus::Reviewed,
|
review_status: ReviewStatus::Reviewed,
|
||||||
confirmation_status: ConfirmationStatus::Confirmed,
|
confirmation_status: ConfirmationStatus::Confirmed,
|
||||||
|
submitter_token: _
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +252,43 @@ pub struct JobOffer<AttachmentLocation> {
|
||||||
pub(crate) links: Vec<Link>,
|
pub(crate) links: Vec<Link>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<V1JobOffer> for JobOffer<PathBuf> {
|
||||||
|
fn from(old: V1JobOffer) -> Self {
|
||||||
|
JobOffer {
|
||||||
|
title: old.title,
|
||||||
|
offering_party: old.offering_party,
|
||||||
|
public_contact_info: old.public_contact_info,
|
||||||
|
date_of_submission: old.date_of_submission,
|
||||||
|
date_of_expiry: old.date_of_expiry,
|
||||||
|
permanent: old.permanent,
|
||||||
|
contact_info: old.contact_info,
|
||||||
|
status: old.status.into(),
|
||||||
|
attachments: old.attachments,
|
||||||
|
links: old.links,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Hash)]
|
||||||
|
pub struct V1JobOffer {
|
||||||
|
pub(crate) title: String,
|
||||||
|
pub(crate) offering_party: String,
|
||||||
|
#[serde(skip_serializing_if = "std::ops::Not::not", default)]
|
||||||
|
pub(crate) public_contact_info: bool,
|
||||||
|
pub(crate) date_of_submission: better_toml_datetime::Datetime,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
|
pub(crate) date_of_expiry: Option<Date>,
|
||||||
|
#[serde(skip_serializing_if = "std::ops::Not::not", default)]
|
||||||
|
pub(crate) permanent: bool,
|
||||||
|
// complex fields need to come after simple ones for derived serialization to work with toml format!
|
||||||
|
pub(crate) contact_info: Address,
|
||||||
|
pub(crate) status: V1JobOfferStatus,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
|
pub(crate) attachments: Vec<Attachment<PathBuf>>,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
|
pub(crate) links: Vec<Link>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for JobOffer<PathBuf> {
|
impl Deref for JobOffer<PathBuf> {
|
||||||
type Target = JobOfferStatus;
|
type Target = JobOfferStatus;
|
||||||
|
|
||||||
|
|
@ -441,7 +514,7 @@ impl JobOffer<PathBuf> {
|
||||||
.map(|attachment| {
|
.map(|attachment| {
|
||||||
let location = match (is_preview && !self.is_published(), confirmation_token) {
|
let location = match (is_preview && !self.is_published(), confirmation_token) {
|
||||||
(false, _) => attachment.generate_link(id, req, None)?.to_string(),
|
(false, _) => attachment.generate_link(id, req, None)?.to_string(),
|
||||||
(true, Some(token)) if self.check_confirmation_token(token) => {
|
(true, Some(token)) if self.check_submitter_token(token) => {
|
||||||
attachment.generate_link(id, req, Some(token))?.to_string()
|
attachment.generate_link(id, req, Some(token))?.to_string()
|
||||||
}
|
}
|
||||||
(true, _) => preview_location.to_string(),
|
(true, _) => preview_location.to_string(),
|
||||||
|
|
@ -560,12 +633,7 @@ impl JobOffers {
|
||||||
let file_content = tokio::fs::read_to_string(&index)
|
let file_content = tokio::fs::read_to_string(&index)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| JobofferLoadError::IO(err, index))?;
|
.map_err(|err| JobofferLoadError::IO(err, index))?;
|
||||||
let job = toml::de::from_str(&file_content).map_err(|err| {
|
let job = Self::load_job_offer(file_content, &key).await?;
|
||||||
JobofferLoadError::TomlDeserializationError {
|
|
||||||
id: key.clone(),
|
|
||||||
err,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
job_offers.insert(key, job);
|
job_offers.insert(key, job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -577,6 +645,32 @@ impl JobOffers {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_job_offer(
|
||||||
|
file_content: String,
|
||||||
|
key: &String,
|
||||||
|
) -> Result<JobOffer<PathBuf>, JobofferLoadError> {
|
||||||
|
let job = toml::de::from_str(&file_content)
|
||||||
|
.map_err(|err| JobofferLoadError::TomlDeserializationError {
|
||||||
|
id: key.clone(),
|
||||||
|
err,
|
||||||
|
})
|
||||||
|
.or_else(|_prev_err| {
|
||||||
|
let v1: V1JobOffer = toml::de::from_str(&file_content).map_err(|err| {
|
||||||
|
JobofferLoadError::TomlDeserializationError {
|
||||||
|
id: key.clone(),
|
||||||
|
err,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
// migration will only be persisted on the next change to the joboffer
|
||||||
|
info!(
|
||||||
|
"In-Memory migration applied while loading joboffer id: {}",
|
||||||
|
key
|
||||||
|
);
|
||||||
|
Ok(v1.into())
|
||||||
|
})?;
|
||||||
|
Ok(job)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn create_new_offer<'data, 'config, Tz>(
|
pub(crate) async fn create_new_offer<'data, 'config, Tz>(
|
||||||
&'data self,
|
&'data self,
|
||||||
submission_time: chrono::DateTime<Tz>,
|
submission_time: chrono::DateTime<Tz>,
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,13 @@ impl JobOfferActions {
|
||||||
.url_for(JOBOFFER_EDIT_ROUTE, &[id])
|
.url_for(JOBOFFER_EDIT_ROUTE, &[id])
|
||||||
.expect("generation of delete route urls should succeed");
|
.expect("generation of delete route urls should succeed");
|
||||||
|
|
||||||
let confirmation_url = match &offer.status.confirmation_status {
|
let confirmation_url = match &offer.status {
|
||||||
ConfirmationStatus::AwaitingConfirmation { token } => {
|
JobOfferStatus {
|
||||||
Some(req.url_for(JOBOFFER_CONFIRM_ROUTE, [id, token])?)
|
confirmation_status: ConfirmationStatus::AwaitingConfirmation,
|
||||||
}
|
submitter_token: Some(token),
|
||||||
ConfirmationStatus::Confirmed => None,
|
review_status: _,
|
||||||
|
} => Some(req.url_for(JOBOFFER_CONFIRM_ROUTE, [id, token])?),
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let highlight_url = offer.highlight_link(id, req)?;
|
let highlight_url = offer.highlight_link(id, req)?;
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ pub(crate) async fn job_offer_attachment(
|
||||||
&& !query
|
&& !query
|
||||||
.token
|
.token
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map_or(false, |token| offer.check_confirmation_token(token))
|
.map_or(false, |token| offer.check_submitter_token(token))
|
||||||
{
|
{
|
||||||
let dest = req
|
let dest = req
|
||||||
.url_for(JOBOFFER_ATTACHMENT_ROUTE, &[id, attachment_name])
|
.url_for(JOBOFFER_ATTACHMENT_ROUTE, &[id, attachment_name])
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ pub(crate) async fn confirm_joboffer_get(
|
||||||
let req_token = &path.1;
|
let req_token = &path.1;
|
||||||
|
|
||||||
if let Some(job_offer) = offers.get_offer(id).await {
|
if let Some(job_offer) = offers.get_offer(id).await {
|
||||||
if !job_offer.check_confirmation_token(req_token) {
|
if !job_offer.check_submitter_token(req_token) {
|
||||||
Err(ConfirmationResponseError::InvalidRequest)
|
Err(ConfirmationResponseError::InvalidRequest)
|
||||||
} else {
|
} else {
|
||||||
let user = User::current(&session).ok();
|
let user = User::current(&session).ok();
|
||||||
|
|
@ -137,7 +137,7 @@ pub(crate) async fn reject_joboffer_post(
|
||||||
let user = User::current(&session).ok();
|
let user = User::current(&session).ok();
|
||||||
|
|
||||||
if let Some(job_offer) = offers.get_offer(id).await {
|
if let Some(job_offer) = offers.get_offer(id).await {
|
||||||
if job_offer.check_confirmation_token(req_token) {
|
if job_offer.check_submitter_token(req_token) {
|
||||||
drop(job_offer);
|
drop(job_offer);
|
||||||
offers.delete_offer(id, false, &config).await?;
|
offers.delete_offer(id, false, &config).await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,9 +228,7 @@ pub(crate) async fn create_job_offer<'data, 'config>(
|
||||||
let confirmation_status = if skip_confirmation {
|
let confirmation_status = if skip_confirmation {
|
||||||
ConfirmationStatus::Confirmed
|
ConfirmationStatus::Confirmed
|
||||||
} else {
|
} else {
|
||||||
ConfirmationStatus::AwaitingConfirmation {
|
ConfirmationStatus::AwaitingConfirmation
|
||||||
token: token.clone(),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let job_offer = JobOffer {
|
let job_offer = JobOffer {
|
||||||
|
|
@ -243,7 +241,7 @@ pub(crate) async fn create_job_offer<'data, 'config>(
|
||||||
permanent: is_permanent,
|
permanent: is_permanent,
|
||||||
attachments: job_offer_form.attachments,
|
attachments: job_offer_form.attachments,
|
||||||
links: job_offer_form.links,
|
links: job_offer_form.links,
|
||||||
status: JobOfferStatus::new(review_status, confirmation_status),
|
status: JobOfferStatus::new(review_status, confirmation_status, token.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let created_offer = offers.create_new_offer(now_date, job_offer, config).await?;
|
let created_offer = offers.create_new_offer(now_date, job_offer, config).await?;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue