From f84748fccb7d30f822311a586c5959295160ca81 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 21 Nov 2022 15:35:32 +0100 Subject: [PATCH] feat: submission progress endpoint --- api/src/lib.rs | 1 + api/src/routes/candidate.rs | 22 +++++- core/src/services/portfolio_service.rs | 102 +++++++++++++++++++++++-- 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 1a793c7..81ee0d1 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -59,6 +59,7 @@ async fn start() -> Result<(), rocket::Error> { routes::candidate::submit_portfolio, routes::candidate::is_portfolio_prepared, routes::candidate::is_portfolio_submitted, + routes::candidate::submission_progress, routes::candidate::download_portfolio, ], ) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index e180352..d73ddee 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -3,7 +3,7 @@ use std::net::SocketAddr; use portfolio_core::candidate_details::ApplicationDetails; use portfolio_core::services::application_service::ApplicationService; use portfolio_core::services::candidate_service::CandidateService; -use portfolio_core::services::portfolio_service::PortfolioService; +use portfolio_core::services::portfolio_service::{PortfolioService, SubmissionProgress}; use requests::LoginRequest; use rocket::http::{Cookie, CookieJar, Status}; use rocket::response::status::Custom; @@ -128,6 +128,26 @@ pub async fn upload_cover_letter( Ok("Letter added".to_string()) } +#[get("/submission_progress")] +pub async fn submission_progress( + conn: Connection<'_, Db>, + session: CandidateAuth +) -> Result, Custom> { + let candidate: entity::candidate::Model = session.into(); + + let progress = PortfolioService::get_submission_progress(candidate.application) + .await + .map_err(|e| { + Custom( + Status::from_code(e.code()).unwrap_or_default(), + e.to_string(), + ) + })?; + + Ok( + Json(progress) + ) +} // TODO: JSON #[get["/is_cover_letter"]] pub async fn is_cover_letter(session: CandidateAuth) -> Result> { diff --git a/core/src/services/portfolio_service.rs b/core/src/services/portfolio_service.rs index fe75805..6917dca 100644 --- a/core/src/services/portfolio_service.rs +++ b/core/src/services/portfolio_service.rs @@ -1,20 +1,66 @@ use std::{path::{PathBuf, Path}}; use entity::candidate; -use sea_orm::DbConn; +use sea_orm::{DbConn}; +use serde::{Serialize, ser::{SerializeStruct}}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{error::ServiceError, Query, crypto}; +pub enum SubmissionProgress { + NoneInCache, + SomeInCache(Vec), + AllInCache, + Submitted, +} + +impl SubmissionProgress { + pub fn index(&self) -> usize { + match self { + SubmissionProgress::NoneInCache => 1, + SubmissionProgress::SomeInCache(_) => 2, + SubmissionProgress::AllInCache => 3, + SubmissionProgress::Submitted => 4, + } + } +} + +// Serialize the enum so that the JSON contains status field and a list of files present in cache +impl Serialize for SubmissionProgress { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut progress = serializer.serialize_struct("SubmissionProgress", 2)?; + progress.serialize_field("status", &self.index())?; + + match self { + SubmissionProgress::SomeInCache(files) => { + progress.serialize_field("files", files)?; + } + _ => { + progress.serialize_field("files", &Vec::::new())?; + } + }; + + progress.end() + } +} + + #[derive(Copy, Clone)] -enum FileType { - CoverLetterPdf, - PortfolioLetterPdf, - PortfolioZip, - Age, +pub enum FileType { + CoverLetterPdf = 1, + PortfolioLetterPdf = 2, + PortfolioZip = 3, + Age = 4, } impl FileType { + pub fn index(&self) -> usize { + *self as usize + } + pub fn as_str(&self) -> &'static str { match self { FileType::CoverLetterPdf => "MOTIVACNI_DOPIS.pdf", @@ -23,6 +69,16 @@ impl FileType { FileType::Age => "PORTFOLIO.age", } } + + pub fn iter_cache() -> impl Iterator { + [ + FileType::CoverLetterPdf, + FileType::PortfolioLetterPdf, + FileType::PortfolioZip, + ] + .iter() + .copied() + } } impl ToString for FileType { @@ -31,9 +87,43 @@ impl ToString for FileType { } } +impl Serialize for FileType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u32(self.index() as u32) + } +} + pub struct PortfolioService; impl PortfolioService { + pub async fn get_submission_progress(candidate_id: i32) -> Result { + let path = Self::get_file_store_path().join(&candidate_id.to_string()); + if !path.exists() { + return Err(ServiceError::CandidateNotFound); + } + let cache_path = path.join("cache"); + + if path.join(FileType::Age.as_str()).exists() { + return Ok(SubmissionProgress::Submitted); + } + + let mut files = Vec::new(); + for file in FileType::iter_cache() { + if cache_path.join(file.as_str()).exists() { + files.push(file); + } + } + match files.len() { + 0 => Ok(SubmissionProgress::NoneInCache), + 3 => Ok(SubmissionProgress::AllInCache), + _ => Ok(SubmissionProgress::SomeInCache(files)), + } + } + + // Get root path or local directory fn get_file_store_path() -> PathBuf { dotenv::dotenv().ok();