mirror of
https://github.com/danbulant/Portfolio
synced 2026-05-27 14:02:14 +00:00
Merge pull request #36 from EETagent/get_candidate_details
Get candidate details endpoint
This commit is contained in:
commit
a6ce24299f
6 changed files with 59 additions and 82 deletions
|
|
@ -1,71 +0,0 @@
|
||||||
use entity::candidate::Model as Candidate;
|
|
||||||
use portfolio_core::sea_orm::prelude::Uuid;
|
|
||||||
use portfolio_core::services::admin_service::AdminService;
|
|
||||||
use portfolio_core::services::candidate_service::CandidateService;
|
|
||||||
use rocket::http::Status;
|
|
||||||
use rocket::outcome::Outcome;
|
|
||||||
use rocket::request::{FromRequest, Request};
|
|
||||||
|
|
||||||
use crate::pool::Db;
|
|
||||||
|
|
||||||
pub struct CandidateAuth(Candidate);
|
|
||||||
|
|
||||||
impl Into<Candidate> for CandidateAuth {
|
|
||||||
fn into(self) -> Candidate {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
|
||||||
impl<'r> FromRequest<'r> for CandidateAuth {
|
|
||||||
type Error = Option<String>;
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> Outcome<CandidateAuth, (Status, Self::Error), ()> {
|
|
||||||
let session_id = req.cookies().get("id").unwrap().name_value().1;
|
|
||||||
let conn = &req.rocket().state::<Db>().unwrap().conn;
|
|
||||||
|
|
||||||
let uuid = match Uuid::parse_str(&session_id) {
|
|
||||||
Ok(uuid) => uuid,
|
|
||||||
Err(_) => return Outcome::Failure((Status::BadRequest, None)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let session = CandidateService::auth(conn, uuid).await;
|
|
||||||
|
|
||||||
match session {
|
|
||||||
Ok(model) => Outcome::Success(CandidateAuth(model)),
|
|
||||||
Err(_) => Outcome::Failure((Status::Unauthorized, None)),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AdminAuth(Candidate);
|
|
||||||
|
|
||||||
impl Into<Candidate> for AdminAuth {
|
|
||||||
fn into(self) -> Candidate {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
|
||||||
impl<'r> FromRequest<'r> for AdminAuth {
|
|
||||||
type Error = Option<String>;
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> Outcome<AdminAuth, (Status, Self::Error), ()> {
|
|
||||||
let session_id = req.cookies().get("id").unwrap().name_value().1;
|
|
||||||
let conn = &req.rocket().state::<Db>().unwrap().conn;
|
|
||||||
|
|
||||||
let uuid = match Uuid::parse_str(&session_id) {
|
|
||||||
Ok(uuid) => uuid,
|
|
||||||
Err(_) => return Outcome::Failure((Status::BadRequest, None)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let session = AdminService::auth(conn, uuid).await;
|
|
||||||
|
|
||||||
match session {
|
|
||||||
Ok(model) => Outcome::Success(AdminAuth(model)),
|
|
||||||
Err(e) => Outcome::Failure(
|
|
||||||
(Status::from_code(e.code()).unwrap_or(Status::InternalServerError), None)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -42,6 +42,7 @@ async fn start() -> Result<(), rocket::Error> {
|
||||||
routes::candidate::login,
|
routes::candidate::login,
|
||||||
routes::candidate::whoami,
|
routes::candidate::whoami,
|
||||||
routes::candidate::fill_details,
|
routes::candidate::fill_details,
|
||||||
|
routes::candidate::get_details,
|
||||||
routes::candidate::upload_cover_letter,
|
routes::candidate::upload_cover_letter,
|
||||||
routes::candidate::upload_portfolio_letter,
|
routes::candidate::upload_portfolio_letter,
|
||||||
routes::candidate::upload_portfolio_zip,
|
routes::candidate::upload_portfolio_zip,
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,9 @@ pub struct AdminLoginRequest {
|
||||||
pub admin_id: i32,
|
pub admin_id: i32,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct PasswordRequest {
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ use rocket::serde::json::Json;
|
||||||
|
|
||||||
use sea_orm_rocket::Connection;
|
use sea_orm_rocket::Connection;
|
||||||
|
|
||||||
|
use crate::requests::PasswordRequest;
|
||||||
use crate::guards::data::letter::Letter;
|
use crate::guards::data::letter::Letter;
|
||||||
use crate::guards::data::portfolio::Portfolio;
|
use crate::guards::data::portfolio::Portfolio;
|
||||||
use crate::{guards::request::auth::CandidateAuth, pool::Db, requests};
|
use crate::{guards::request::auth::CandidateAuth, pool::Db, requests};
|
||||||
|
|
@ -73,6 +74,26 @@ pub async fn fill_details(
|
||||||
Ok("Details added".to_string())
|
Ok("Details added".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/get_details", data = "<password_form>")]
|
||||||
|
pub async fn get_details(
|
||||||
|
conn: Connection<'_, Db>,
|
||||||
|
password_form: Json<PasswordRequest>,
|
||||||
|
session: CandidateAuth,
|
||||||
|
) -> Result<Json<UserDetails>, Custom<String>> {
|
||||||
|
let db = conn.into_inner();
|
||||||
|
let candidate: entity::candidate::Model = session.into();
|
||||||
|
let password = password_form.password.clone();
|
||||||
|
|
||||||
|
// let handle = tokio::spawn(async move {
|
||||||
|
let details = CandidateService::decrypt_details(db, candidate.application, password).await.map_err(|e| {
|
||||||
|
Custom(
|
||||||
|
Status::from_code(e.code()).unwrap_or_default(),
|
||||||
|
e.message(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
details.map(|d| Json(d))
|
||||||
|
}
|
||||||
#[post("/coverletter", data = "<letter>")]
|
#[post("/coverletter", data = "<letter>")]
|
||||||
pub async fn upload_cover_letter(
|
pub async fn upload_cover_letter(
|
||||||
session: CandidateAuth,
|
session: CandidateAuth,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::error::ServiceError;
|
||||||
|
|
||||||
/// Foolproof random 8 char string
|
/// Foolproof random 8 char string
|
||||||
/// only uppercase letters (except for 0 and O) and numbers
|
/// only uppercase letters (except for 0 and O) and numbers
|
||||||
/// TODO tests
|
/// TODO tests
|
||||||
|
|
@ -265,14 +267,18 @@ pub async fn encrypt_password_with_recipients(
|
||||||
pub async fn decrypt_password_with_private_key(
|
pub async fn decrypt_password_with_private_key(
|
||||||
password_encrypted: &str,
|
password_encrypted: &str,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, ServiceError> { // TODO More specific error handling
|
||||||
let encrypted = base64::decode(password_encrypted)?;
|
let Ok(encrypted) = base64::decode(password_encrypted) else {
|
||||||
|
return Err(ServiceError::CryptoEncryptFailed);
|
||||||
|
};
|
||||||
|
|
||||||
let mut decrypt_buffer = Vec::new();
|
let mut decrypt_buffer = Vec::new();
|
||||||
|
|
||||||
age_decrypt_with_private_key(encrypted.as_slice(), &mut decrypt_buffer, key).await?;
|
if age_decrypt_with_private_key(encrypted.as_slice(), &mut decrypt_buffer, key).await.is_err() {
|
||||||
|
return Err(ServiceError::CryptoDecryptFailed);
|
||||||
|
};
|
||||||
|
|
||||||
Ok(String::from_utf8(decrypt_buffer)?)
|
String::from_utf8(decrypt_buffer).map_err(|_| ServiceError::CryptoDecryptFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encrypt_file_with_recipients<P: AsRef<Path>>(
|
pub async fn encrypt_file_with_recipients<P: AsRef<Path>>(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use entity::candidate;
|
use entity::candidate;
|
||||||
use sea_orm::{prelude::Uuid, DbConn};
|
use sea_orm::{prelude::Uuid, DbConn};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::{self, hash_password},
|
crypto::{self, hash_password},
|
||||||
|
|
@ -169,7 +169,7 @@ impl EncryptedUserDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct UserDetails {
|
pub struct UserDetails {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub surname: String,
|
pub surname: String,
|
||||||
|
|
@ -268,11 +268,10 @@ impl CandidateService {
|
||||||
candidate_id: i32,
|
candidate_id: i32,
|
||||||
password: String,
|
password: String,
|
||||||
) -> Result<UserDetails, ServiceError> {
|
) -> Result<UserDetails, ServiceError> {
|
||||||
// compare passwords // TODO: login in api?? // TODO: dedicated function
|
let candidate = match Query::find_candidate_by_id(db, candidate_id).await {
|
||||||
let candidate = Query::find_candidate_by_id(db, candidate_id)
|
Ok(candidate) => candidate.unwrap(),
|
||||||
.await
|
Err(_) => return Err(ServiceError::DbError), // TODO: logging
|
||||||
.map_err(|_| ServiceError::DbError)?
|
};
|
||||||
.ok_or(ServiceError::UserNotFound)?;
|
|
||||||
|
|
||||||
match crypto::verify_password((&password).to_string(), candidate.code.clone()).await {
|
match crypto::verify_password((&password).to_string(), candidate.code.clone()).await {
|
||||||
Ok(valid) => {
|
Ok(valid) => {
|
||||||
|
|
@ -292,6 +291,21 @@ impl CandidateService {
|
||||||
enc_details.decrypt(dec_priv_key).await
|
enc_details.decrypt(dec_priv_key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn is_set_up(
|
||||||
|
candidate: &candidate::Model,
|
||||||
|
) -> bool {
|
||||||
|
candidate.name.is_some() &&
|
||||||
|
candidate.surname.is_some() &&
|
||||||
|
candidate.birthplace.is_some() &&
|
||||||
|
// birthdate: NaiveDate::from_ymd(2000, 1, 1),
|
||||||
|
candidate.address.is_some() &&
|
||||||
|
candidate.telephone.is_some() &&
|
||||||
|
candidate.citizenship.is_some() &&
|
||||||
|
candidate.email.is_some() &&
|
||||||
|
candidate.sex.is_some() &&
|
||||||
|
candidate.study.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn add_cover_letter(candidate_id: i32, letter: Vec<u8>) -> Result<(), ServiceError> {
|
pub async fn add_cover_letter(candidate_id: i32, letter: Vec<u8>) -> Result<(), ServiceError> {
|
||||||
// TODO
|
// TODO
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue